diff --git a/.clabot b/.clabot index 657334305..f5a373449 100644 --- a/.clabot +++ b/.clabot @@ -15,7 +15,8 @@ "ajbruin", "hoejmann", "brandsimon", - "sideeffffect" + "sideeffffect", + "T0astBread" ], "label": "cla-signed ✔️", "message": "Thank you for your pull request and welcome to our community! We require contributors to sign our [Contributor License Agreement](https://github.com/grote/Transportr/blob/master/CLA.md), and we don't seem to have the user {{usersWithoutCLA}} on file. In order for your code to get reviewed and merged, please explicitly state that you accept the agreement." diff --git a/app/src/main/java/de/grobox/transportr/TransportrActivity.kt b/app/src/main/java/de/grobox/transportr/TransportrActivity.kt index a0cec2a20..9e6838c6c 100644 --- a/app/src/main/java/de/grobox/transportr/TransportrActivity.kt +++ b/app/src/main/java/de/grobox/transportr/TransportrActivity.kt @@ -30,6 +30,7 @@ import de.grobox.transportr.networks.PickTransportNetworkActivity import de.grobox.transportr.networks.PickTransportNetworkActivity.Companion.FORCE_NETWORK_SELECTION import de.grobox.transportr.networks.TransportNetworkManager import de.grobox.transportr.settings.SettingsManager +import de.grobox.transportr.utils.TransportrUtils import java.util.* import javax.inject.Inject @@ -50,6 +51,7 @@ abstract class TransportrActivity : AppCompatActivity() { useLanguage() AppCompatDelegate.setDefaultNightMode(settingsManager.theme) ensureTransportNetworkSelected() + TransportrUtils.updateGlobalHttpProxy(settingsManager.getProxy(emptyMap()), manager) super.onCreate(savedInstanceState) } @@ -120,5 +122,4 @@ abstract class TransportrActivity : AppCompatActivity() { finish() } } - } diff --git a/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt b/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt index 5f407f27c..b4d057965 100644 --- a/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt +++ b/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt @@ -23,6 +23,7 @@ import android.content.Context import android.location.Geocoder import androidx.annotation.WorkerThread import com.mapbox.mapboxsdk.geometry.LatLng +import de.grobox.transportr.settings.SettingsManager import de.grobox.transportr.utils.hasLocation import de.schildbach.pte.dto.Location import de.schildbach.pte.dto.LocationType.ADDRESS @@ -31,12 +32,17 @@ import okhttp3.* import org.json.JSONException import org.json.JSONObject import java.io.IOException +import java.net.Proxy import java.util.* +import javax.inject.Inject import kotlin.concurrent.thread class ReverseGeocoder(private val context: Context, private val callback: ReverseGeocoderCallback) { + @Inject + lateinit var settingsManager: SettingsManager + fun findLocation(location: Location) { if (!location.hasLocation()) return findLocation(location.latAsDouble, location.lonAsDouble) @@ -82,7 +88,9 @@ class ReverseGeocoder(private val context: Context, private val callback: Revers } private fun findLocationWithOsm(lat: Double, lon: Double) { - val client = OkHttpClient() + val client = OkHttpClient.Builder() + .proxy(settingsManager.getProxy(emptyMap())) + .build() // https://nominatim.openstreetmap.org/reverse?lat=52.5217&lon=13.4324&format=json val url = StringBuilder("https://nominatim.openstreetmap.org/reverse?") diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt index 17447399a..ecfd696d0 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt @@ -23,6 +23,9 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.os.Bundle +import android.util.Log +import android.util.Patterns +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatDelegate.* import androidx.core.app.ActivityCompat import androidx.core.app.ActivityOptionsCompat @@ -36,7 +39,13 @@ import de.grobox.transportr.networks.PickTransportNetworkActivity import de.grobox.transportr.networks.TransportNetwork import de.grobox.transportr.networks.TransportNetworkManager import de.grobox.transportr.settings.SettingsManager.Companion.LANGUAGE +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_ENABLE +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_HOST +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PORT +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PROTOCOL import de.grobox.transportr.settings.SettingsManager.Companion.THEME +import de.grobox.transportr.ui.ValidatedEditTextPreference +import de.grobox.transportr.utils.TransportrUtils import javax.inject.Inject class SettingsFragment : PreferenceFragmentCompat() { @@ -45,6 +54,9 @@ class SettingsFragment : PreferenceFragmentCompat() { val TAG: String = SettingsFragment::class.java.simpleName } + @Inject + internal lateinit var settingsManager: SettingsManager + @Inject internal lateinit var manager: TransportNetworkManager private lateinit var networkPref: Preference @@ -91,6 +103,39 @@ class SettingsFragment : PreferenceFragmentCompat() { true } } + + arrayOf(PROXY_ENABLE, PROXY_HOST, PROXY_PORT, PROXY_PROTOCOL).forEach { prefKey -> + (findPreference(prefKey) as Preference?)?.let { pref -> + pref.setOnPreferenceChangeListener { _, newValue -> + try { + val newProxy = settingsManager.getProxy(mapOf( + Pair(prefKey, newValue) + )) + TransportrUtils.checkInternetConnectionViaProxy(newProxy) + TransportrUtils.updateGlobalHttpProxy(newProxy, manager) + } catch (e: Exception) { + Log.e(TAG, "Invalid proxy settings: " + e.message) + AlertDialog.Builder(context!!) + .setTitle(R.string.invalid_proxy_settings) + .setMessage(e.message) + .setPositiveButton(android.R.string.ok) { di, _ -> di.dismiss() } + .create() + .show() + } + true + } + } + } + + findPreference(PROXY_HOST)!!.validate = { value -> + value == "localhost" + || Patterns.DOMAIN_NAME.matcher(value).matches() + || Patterns.IP_ADDRESS.matcher(value).matches() + } + + findPreference(PROXY_PORT)!!.validate = { value -> + value.toIntOrNull() in 1..65534 + } } private fun onTransportNetworkChanged(network: TransportNetwork) { diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt index 56b247ad0..bee7e652c 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt @@ -30,8 +30,16 @@ import de.grobox.transportr.R import de.schildbach.pte.NetworkId import de.schildbach.pte.NetworkProvider.Optimize import de.schildbach.pte.NetworkProvider.WalkSpeed +import okhttp3.OkHttpClient +import okhttp3.Request +import java.lang.Exception +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Proxy +import java.net.UnknownHostException import java.util.* import javax.inject.Inject +import kotlin.Throws class SettingsManager @Inject constructor(private val context: Context) { @@ -92,6 +100,23 @@ class SettingsManager @Inject constructor(private val context: Context) { } } + @Throws(UnknownHostException::class, IllegalStateException::class) + fun getProxy(proxyPrefOverrides: Map): Proxy { + val isEnabled = proxyPrefOverrides[PROXY_ENABLE] as Boolean? ?: settings.getBoolean(PROXY_ENABLE, false) + if (!isEnabled) + return Proxy.NO_PROXY + val typeStr = proxyPrefOverrides[PROXY_PROTOCOL] as String? ?: settings.getString(PROXY_PROTOCOL, null) + val type = when (typeStr) { + "SOCKS" -> Proxy.Type.SOCKS + "HTTP" -> Proxy.Type.HTTP + else -> throw IllegalStateException("Illegal proxy type: " + typeStr) + } + val host = proxyPrefOverrides[PROXY_HOST] as String? ?: settings.getString(PROXY_HOST, null) + val portStr = proxyPrefOverrides[PROXY_PORT] as String? ?: settings.getString(PROXY_PORT, null) + val port = Integer.parseInt(portStr!!) + return Proxy(type, InetSocketAddress(InetAddress.getByName(host), port)) + } + fun showLocationFragmentOnboarding(): Boolean = settings.getBoolean(LOCATION_ONBOARDING, true) fun locationFragmentOnboardingShown() { settings.edit().putBoolean(LOCATION_ONBOARDING, false).apply() @@ -136,6 +161,8 @@ class SettingsManager @Inject constructor(private val context: Context) { } companion object { + private const val TAG = "SettingsManager" + private const val NETWORK_ID_1 = "NetworkId" private const val NETWORK_ID_2 = "NetworkId2" private const val NETWORK_ID_3 = "NetworkId3" @@ -147,6 +174,10 @@ class SettingsManager @Inject constructor(private val context: Context) { private const val OPTIMIZE = "pref_key_optimize" private const val LOCATION_ONBOARDING = "locationOnboarding" private const val TRIP_DETAIL_ONBOARDING = "tripDetailOnboarding" + internal const val PROXY_ENABLE = "pref_key_proxy_enable" + internal const val PROXY_PROTOCOL = "pref_key_proxy_protocol" + internal const val PROXY_HOST = "pref_key_proxy_host" + internal const val PROXY_PORT = "pref_key_proxy_port" } } diff --git a/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt b/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt new file mode 100644 index 000000000..5be931c1d --- /dev/null +++ b/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt @@ -0,0 +1,114 @@ +/* + * Transportr + * + * Copyright (c) 2013 - 2020 Torsten Grote + * + * This program is Free Software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.grobox.transportr.ui + +import android.content.Context +import android.content.res.TypedArray +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.inputmethod.InputMethodManager +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.preference.Preference +import de.grobox.transportr.R + +/** + * An editable text preference that gives immediate user feedback by + * disabling the "OK" button of the edit dialog if the entered text + * does not match the validation criteria. + * + * Validation can be configured by setting the validate + * property. + */ +class ValidatedEditTextPreference(ctx: Context, attrs: AttributeSet) : + Preference(ctx, attrs, R.attr.preferenceStyle) { + + var validate: ((String) -> Boolean) = { true } + private var persistedValue: String = "" + private var currentValue: String = persistedValue + + init { + setSummaryProvider { persistedValue } + + setOnPreferenceClickListener { + currentValue = persistedValue + + val dialog = AlertDialog.Builder(ctx) + .setTitle(title) + .setView(R.layout.dialog_validated_edit_text_preference) + .setCancelable(true) + .setPositiveButton(R.string.ok) { dialogInterface, _ -> + dialogInterface.dismiss() + if (callChangeListener(currentValue)) { + persistString(currentValue) + persistedValue = currentValue + notifyChanged() + } + } + .setNegativeButton(R.string.cancel) { dialogInterface, _ -> + dialogInterface.cancel() + currentValue = persistedValue + } + .create() + dialog.show() + + val editText = dialog.findViewById(R.id.text_input)!! + val okButton = dialog.findViewById