diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_additional_option.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_additional_option.png new file mode 100644 index 0000000000..c825caeeb3 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_additional_option.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_additional_option.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_additional_option.png new file mode 100644 index 0000000000..d7df7ace35 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_additional_option.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_additional_option.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_additional_option.png new file mode 100644 index 0000000000..ba244c0a10 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_additional_option.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_additional_option.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_additional_option.png new file mode 100644 index 0000000000..0043a874e8 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_additional_option.png differ diff --git a/OsmAnd-telegram/res/drawable-xxxhdpi/ic_action_additional_option.png b/OsmAnd-telegram/res/drawable-xxxhdpi/ic_action_additional_option.png new file mode 100644 index 0000000000..9b7c6fa3f4 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxxhdpi/ic_action_additional_option.png differ diff --git a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml index bd1bb922e1..a3a9f4a177 100644 --- a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml +++ b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml @@ -104,6 +104,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/item_with_descr_and_right_switch.xml b/OsmAnd-telegram/res/layout/item_with_descr_and_right_switch.xml index 4969b003ed..9f716ffc61 100644 --- a/OsmAnd-telegram/res/layout/item_with_descr_and_right_switch.xml +++ b/OsmAnd-telegram/res/layout/item_with_descr_and_right_switch.xml @@ -50,6 +50,18 @@ + + 28dp 52dp + 140dp 6dp @@ -73,6 +74,8 @@ 300dp + 60dp + 22sp diff --git a/OsmAnd-telegram/res/values/strings.xml b/OsmAnd-telegram/res/values/strings.xml index 3712668b0a..59a0e6d26a 100644 --- a/OsmAnd-telegram/res/values/strings.xml +++ b/OsmAnd-telegram/res/values/strings.xml @@ -1,5 +1,19 @@ + Key + Password + Username + Credentials + Port + Server + Connection + Enable + Proxy type + Connected + Disconnected + Proxy Settings + Proxy + Privacy Direction Precision Altitude diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt index bbe431bbf8..34bbd5bda3 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt @@ -85,6 +85,9 @@ private const val MONITORING_ENABLED = "monitoring_enabled" private const val SHOW_GPS_POINTS = "show_gps_points" +private const val PROXY_ENABLED = "proxy_enabled" +private const val PROXY_PREFERENCES_KEY = "proxy_preferences" + private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes private const val WAITING_TDLIB_TIME = 30 // 2 seconds @@ -103,6 +106,9 @@ class TelegramSettings(private val app: TelegramApplication) { var currentSharingMode = "" private set + var currentProxyPref: ProxyPref = ProxySOCKS5Pref(-1, "", -1, "", "") + private set + var metricsConstants = MetricsConstants.KILOMETERS_AND_METERS var speedConstants = SpeedConstants.KILOMETERS_PER_HOUR @@ -124,9 +130,12 @@ class TelegramSettings(private val app: TelegramApplication) { var showGpsPoints = false + var proxyEnabled = false + init { updatePrefs() read() + applyProxyPref() } fun hasAnyChatToShareLocation() = shareChatsInfo.isNotEmpty() @@ -202,6 +211,30 @@ class TelegramSettings(private val app: TelegramApplication) { currentSharingMode = sharingMode } + fun updateCurrentProxyPref(proxyPref: ProxyPref, proxyEnabled: Boolean) { + this.proxyEnabled = proxyEnabled + currentProxyPref = proxyPref + applyProxyPref() + } + + fun updateProxySetting(enable: Boolean) { + this.proxyEnabled = enable + if (enable) { + app.telegramHelper.enableProxy(currentProxyPref.id) + } else { + app.telegramHelper.disableProxy() + } + } + + fun applyProxyPref() { + val proxyId = currentProxyPref.id + if (proxyId != -1) { + app.telegramHelper.editProxyPref(currentProxyPref, proxyEnabled) + } else { + app.telegramHelper.addProxyPref(currentProxyPref, proxyEnabled) + } + } + fun prepareForSharingNewMessages() { shareChatsInfo.forEach { (_, shareInfo) -> prepareForSharingNewMessages(shareInfo) @@ -536,6 +569,8 @@ class TelegramSettings(private val app: TelegramApplication) { edit.putBoolean(SHOW_GPS_POINTS, showGpsPoints) + edit.putBoolean(PROXY_ENABLED, proxyEnabled) + val jArray = convertShareChatsInfoToJson() if (jArray != null) { edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString()) @@ -546,6 +581,11 @@ class TelegramSettings(private val app: TelegramApplication) { edit.putString(SHARE_DEVICES_KEY, jsonObject.toString()) } + val jsonObjectProxy = convertProxyPrefToJson() + if (jsonObjectProxy != null) { + edit.putString(PROXY_PREFERENCES_KEY, jsonObjectProxy.toString()) + } + edit.apply() } @@ -597,7 +637,14 @@ class TelegramSettings(private val app: TelegramApplication) { monitoringEnabled = prefs.getBoolean(MONITORING_ENABLED,false) - showGpsPoints = prefs.getBoolean(SHOW_GPS_POINTS,false) + showGpsPoints = prefs.getBoolean(SHOW_GPS_POINTS, false) + + proxyEnabled = prefs.getBoolean(PROXY_ENABLED, false) + try { + parseProxyPreferences(JSONObject(prefs.getString(PROXY_PREFERENCES_KEY, ""))) + } catch (e: JSONException) { + e.printStackTrace() + } } private fun convertShareDevicesToJson():JSONObject?{ @@ -621,6 +668,27 @@ class TelegramSettings(private val app: TelegramApplication) { } } + private fun convertProxyPrefToJson(): JSONObject? { + return try { + val proxyPref = currentProxyPref + JSONObject().apply { + put(ProxyPref.PROXY_ID, proxyPref.id) + put(ProxyPref.TYPE_ID, proxyPref.type) + put(ProxyPref.SERVER_ID, proxyPref.server) + put(ProxyPref.PORT_ID, proxyPref.port) + if (proxyPref is ProxyMTProtoPref) { + put(ProxyMTProtoPref.KEY_ID, proxyPref.key) + } else if (proxyPref is ProxySOCKS5Pref) { + put(ProxySOCKS5Pref.LOGIN_ID, proxyPref.login) + put(ProxySOCKS5Pref.PASSWORD_ID, proxyPref.password) + } + } + } catch (e: JSONException) { + e.printStackTrace() + null + } + } + private fun convertShareChatsInfoToJson(): JSONArray? { return try { val jArray = JSONArray() @@ -678,6 +746,28 @@ class TelegramSettings(private val app: TelegramApplication) { } } + private fun parseProxyPreferences(jsonObject: JSONObject) { + val proxyId = jsonObject.optInt(ProxyPref.PROXY_ID) + val typeString = jsonObject.optString(ProxyPref.TYPE_ID) + val server = jsonObject.optString(ProxyPref.SERVER_ID) + val port = jsonObject.optInt(ProxyPref.PORT_ID) + val proxyPref = when { + ProxyType.valueOf(typeString) == ProxyType.MTPROTO -> { + val key = jsonObject.optString(ProxyMTProtoPref.KEY_ID) + ProxyMTProtoPref(proxyId, server, port, key) + } + ProxyType.valueOf(typeString) == ProxyType.SOCKS5 -> { + val login = jsonObject.optString(ProxySOCKS5Pref.LOGIN_ID) + val password = jsonObject.optString(ProxySOCKS5Pref.PASSWORD_ID) + ProxySOCKS5Pref(proxyId, server, port, login, password) + } + else -> null + } + if (proxyPref != null) { + currentProxyPref = proxyPref + } + } + private fun parseShareDevices(json: String) { shareDevices = OsmandApiUtils.parseJsonContents(json).toHashSet() } @@ -926,6 +1016,45 @@ class TelegramSettings(private val app: TelegramApplication) { } } + enum class ProxyType { + MTPROTO, SOCKS5 + } + + abstract class ProxyPref( + var id: Int, + var type: ProxyType, + open var server: String, + open var port: Int + ) { + companion object { + internal const val PROXY_ID = "proxyId" + internal const val TYPE_ID = "type" + internal const val SERVER_ID = "serverId" + internal const val PORT_ID = "portId" + } + } + + class ProxyMTProtoPref(id: Int, server: String, port: Int, var key: String) : + ProxyPref(id, ProxyType.MTPROTO, server, port) { + companion object { + internal const val KEY_ID = "key" + } + } + + class ProxySOCKS5Pref( + id: Int, + server: String, + port: Int, + var login: String, + var password: String + ) : + ProxyPref(id, ProxyType.SOCKS5, server, port) { + companion object { + internal const val LOGIN_ID = "login" + internal const val PASSWORD_ID = "password" + } + } + class SharingStatus { var title: String = "" diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index 8b5a47f399..63659c5c56 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -683,6 +683,80 @@ class TelegramHelper private constructor() { } } + fun disableProxy() { + client?.send(TdApi.DisableProxy()) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Ok.CONSTRUCTOR -> { + } + } + } + } + + fun enableProxy(proxyId: Int) { + client?.send(TdApi.EnableProxy(proxyId)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Ok.CONSTRUCTOR -> { + } + } + } + } + + fun addProxyPref(proxyPref: TelegramSettings.ProxyPref, enable: Boolean) { + val proxyType: TdApi.ProxyType? = when (proxyPref) { + is TelegramSettings.ProxyMTProtoPref -> TdApi.ProxyTypeMtproto(proxyPref.key) + is TelegramSettings.ProxySOCKS5Pref -> TdApi.ProxyTypeSocks5(proxyPref.login, proxyPref.password) + else -> null + } + client?.send(TdApi.AddProxy(proxyPref.server, proxyPref.port, enable, proxyType)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Proxy.CONSTRUCTOR -> { + val proxy = (obj as TdApi.Proxy) + proxyPref.id = proxy.id + } + } + } + } + + fun editProxyPref(proxyPref: TelegramSettings.ProxyPref, enable: Boolean) { + val proxyType: TdApi.ProxyType? = when (proxyPref) { + is TelegramSettings.ProxyMTProtoPref -> TdApi.ProxyTypeMtproto(proxyPref.key) + is TelegramSettings.ProxySOCKS5Pref -> TdApi.ProxyTypeSocks5(proxyPref.login, proxyPref.password) + else -> null + } + client?.send(TdApi.EditProxy(proxyPref.id, proxyPref.server, proxyPref.port, enable, proxyType)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Proxy.CONSTRUCTOR -> { + val proxy = (obj as TdApi.Proxy) + proxyPref.id = proxy.id + } + } + } + } + fun createPrivateChatWithUser( userId: Int, shareInfo: TelegramSettings.ShareChatInfo, diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/ProxySettingsDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/ProxySettingsDialogFragment.kt new file mode 100644 index 0000000000..c132b65d74 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/ProxySettingsDialogFragment.kt @@ -0,0 +1,218 @@ +package net.osmand.telegram.ui + +import android.os.Build +import android.os.Bundle +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.content.ContextCompat +import android.support.v7.widget.Toolbar +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.* +import net.osmand.telegram.R +import net.osmand.telegram.TelegramSettings.ProxyType +import net.osmand.telegram.TelegramSettings.ProxyPref +import net.osmand.telegram.TelegramSettings.ProxyMTProtoPref +import net.osmand.telegram.TelegramSettings.ProxySOCKS5Pref + +class ProxySettingsDialogFragment : BaseDialogFragment() { + + private val uiUtils get() = app.uiUtils + + private lateinit var mainView: View + private lateinit var proxyEnableSwitcher: Switch + private lateinit var saveButtonContainer: LinearLayout + + private lateinit var selectedProxyType: ProxyType + + private lateinit var serverEditText: EditText + private lateinit var portEditText: EditText + + override fun onCreateView( + inflater: LayoutInflater, + parent: ViewGroup?, + savedInstanceState: Bundle? + ): View { + mainView = inflater.inflate(R.layout.fragment_proxy_settings_dialog, parent) + + val window = dialog.window + if (window != null) { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) + if (Build.VERSION.SDK_INT >= 21) { + window.statusBarColor = ContextCompat.getColor(app, R.color.card_bg_light) + } + } + mainView.findViewById(R.id.toolbar).apply { + navigationIcon = uiUtils.getThemedIcon(R.drawable.ic_arrow_back) + setNavigationOnClickListener { dismiss() } + } + + selectedProxyType = settings.currentProxyPref.type + + mainView.findViewById(R.id.enable_proxy_btn).apply { + val title = findViewById(R.id.title).apply { + text = if (settings.proxyEnabled) getText(R.string.shared_string_disable) else getText( + R.string.shared_string_enable + ) + } + proxyEnableSwitcher = findViewById(R.id.switcher).apply { + isChecked = settings.proxyEnabled + } + setOnClickListener { + val checked = !proxyEnableSwitcher.isChecked + proxyEnableSwitcher.isChecked = checked + title.text = if (checked) getText(R.string.shared_string_disable) else getText(R.string.shared_string_enable) + updateSaveButtonVisibility(checked == settings.proxyEnabled) + } + } + + val container = mainView.findViewById(R.id.proxy_type_container) + ProxyType.values().forEach { + addItemToContainer(inflater, container, it) + } + + serverEditText = mainView.findViewById(R.id.server_edit_text).apply { + val server = settings.currentProxyPref.server + setText(server) + setSelection(server.length) + addTextChangedListener(object : + TextWatcher { + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun afterTextChanged(s: Editable) { + updateSaveButtonVisibility(s.isNotEmpty() && portEditText.text.isNotEmpty()) + } + }) + } + portEditText = mainView.findViewById(R.id.port_edit_text).apply { + val port = settings.currentProxyPref.port + setText(if (port != -1) port.toString() else "") + addTextChangedListener(object : + TextWatcher { + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun afterTextChanged(s: Editable) { + updateSaveButtonVisibility(s.isNotEmpty() && serverEditText.text.isNotEmpty()) + } + }) + } + saveButtonContainer = mainView.findViewById(R.id.save_button_Container).apply { + findViewById(R.id.primary_btn).apply { + text = getString(R.string.shared_string_save) + setOnClickListener { + saveChanges() + updateSaveButtonVisibility(false) + targetFragment?.also { target -> + target.onActivityResult(targetRequestCode, PROXY_PREFERENCES_UPDATED_REQUEST_CODE, null) + } + dismiss() + } + } + } + + updateSelectedProxyType() + updateEditingMode() + updateProxyPrefInfo() + updateSaveButtonVisibility(false) + + return mainView + } + + private fun updateSaveButtonVisibility(visible: Boolean) { + saveButtonContainer.visibility = if (visible) View.VISIBLE else View.GONE + } + + private fun saveChanges() { + val proxyPref = getSelectedProxyPref() + settings.updateCurrentProxyPref(proxyPref, proxyEnableSwitcher.isChecked) + } + + private fun updateProxyPrefInfo() { + val proxyPref = settings.currentProxyPref + if (proxyPref is ProxyMTProtoPref) { + mainView.findViewById(R.id.key_text).text = proxyPref.key + } else if (proxyPref is ProxySOCKS5Pref) { + mainView.findViewById(R.id.username_text).text = proxyPref.login + mainView.findViewById(R.id.password_text).text = proxyPref.password + } + } + + private fun getSelectedProxyPref(): ProxyPref { + val server = serverEditText.text.toString() + val port = portEditText.text.toString().toIntOrNull() ?: -1 + return when (selectedProxyType) { + ProxyType.MTPROTO -> { + val key = mainView.findViewById(R.id.key_text).text.toString() + ProxyMTProtoPref(settings.currentProxyPref.id, server, port, key) + } + ProxyType.SOCKS5 -> { + val username = mainView.findViewById(R.id.username_text).text.toString() + val password = mainView.findViewById(R.id.password_text).text.toString() + ProxySOCKS5Pref(settings.currentProxyPref.id, server, port, username, password) + } + } + } + + private fun updateSelectedProxyType() { + view?.findViewById(R.id.proxy_type_container)?.apply { + for (i in 0 until childCount) { + getChildAt(i).apply { + findViewById(R.id.radio_button).isChecked = tag == selectedProxyType + } + } + } + } + + private fun updateEditingMode() { + mainView.findViewById(R.id.proxy_sosks5_container)?.visibility = + if (selectedProxyType == ProxyType.SOCKS5) View.VISIBLE else View.GONE + mainView.findViewById(R.id.proxy_mtproto_container)?.visibility = + if (selectedProxyType == ProxyType.MTPROTO) View.VISIBLE else View.GONE + } + + private fun addItemToContainer( + inflater: LayoutInflater, + container: ViewGroup, + proxyTypeTag: ProxyType + ) { + inflater.inflate(R.layout.item_with_rb_and_btn, container, false).apply { + findViewById(R.id.title).text = proxyTypeTag.name + findViewById(R.id.primary_btn).visibility = View.GONE + findViewById(R.id.icon).visibility = View.GONE + findViewById(R.id.radio_button).isChecked = selectedProxyType == proxyTypeTag + + setOnClickListener { + selectedProxyType = proxyTypeTag + updateSelectedProxyType() + updateEditingMode() + updateSaveButtonVisibility(true) + } + this.tag = proxyTypeTag + container.addView(this) + } + } + + companion object { + + private const val TAG = "ProxySettingsDialogFragment" + const val PROXY_PREFERENCES_UPDATED_REQUEST_CODE = 6 + + fun showInstance(fm: FragmentManager, target: Fragment): Boolean { + return try { + ProxySettingsDialogFragment().apply { + setTargetFragment(target, PROXY_PREFERENCES_UPDATED_REQUEST_CODE) + show(fm, TAG) + } + true + } catch (e: RuntimeException) { + false + } + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt index 7bc2dc3035..71b5fe9a29 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt @@ -92,6 +92,32 @@ class SettingsDialogFragment : BaseDialogFragment() { container.addView(this) } + container = mainView.findViewById(R.id.proxy_settings_container) + inflater.inflate(R.layout.item_with_descr_and_right_switch, container, false).apply { + findViewById(R.id.icon).setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_proxy)) + findViewById(R.id.icon_right).apply { + visibility = View.VISIBLE + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_additional_option)) + setOnClickListener { + activity?.supportFragmentManager?.also { ProxySettingsDialogFragment.showInstance(it, this@SettingsDialogFragment) } + } + } + findViewById(R.id.title).text = getText(R.string.proxy) + val description = findViewById(R.id.description).apply { + text = if (settings.proxyEnabled) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected) + } + val switcher = findViewById(R.id.switcher).apply { + isChecked = app.settings.proxyEnabled + } + setOnClickListener { + val checked = !app.settings.proxyEnabled + switcher.isChecked = checked + settings.updateProxySetting(checked) + description.text = if (checked) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected) + } + container.addView(this) + } + shareAsDescription = mainView.findViewById(R.id.share_as_description).apply { text = getText(R.string.share_location_as_description) setOnClickListener { @@ -214,6 +240,12 @@ class SettingsDialogFragment : BaseDialogFragment() { } } } + ProxySettingsDialogFragment.PROXY_PREFERENCES_UPDATED_REQUEST_CODE -> { + view?.findViewById(R.id.proxy_settings_container)?.apply { + findViewById(R.id.description)?.text = if (settings.proxyEnabled) getText(R.string.proxy_connected) else getText(R.string.proxy_disconnected) + findViewById(R.id.switcher)?.isChecked = app.settings.proxyEnabled + } + } } }