parent
e3f6ecda80
commit
9b46b0a41b
|
@ -125,10 +125,10 @@ public interface SharedPreferenceConstants {
|
|||
@ExportablePreference(BOOLEAN)
|
||||
String KEY_ENABLE_PROXY = "enable_proxy";
|
||||
@ExportablePreference(STRING)
|
||||
String KEY_PROXY_HOST = "proxy_host";
|
||||
@ExportablePreference(STRING)
|
||||
String KEY_PROXY_TYPE = "proxy_type";
|
||||
@ExportablePreference(STRING)
|
||||
String KEY_PROXY_ADDRESS = "proxy_address";
|
||||
String KEY_PROXY_HOST = "proxy_host";
|
||||
String KEY_PROXY_PORT = "proxy_port";
|
||||
@ExportablePreference(STRING)
|
||||
String KEY_PROXY_USERNAME = "proxy_username";
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.annotation;
|
||||
|
||||
import android.support.annotation.StringDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@StringDef({ProxyType.HTTP, ProxyType.REVERSE})
|
||||
public @interface ProxyType {
|
||||
String HTTP = "http";
|
||||
String REVERSE = "reverse";
|
||||
}
|
|
@ -7,16 +7,15 @@ import android.support.v4.util.ArraySet
|
|||
import android.text.TextUtils
|
||||
import org.mariotaku.kpreferences.*
|
||||
import org.mariotaku.ktextension.bcp47Tag
|
||||
import org.mariotaku.ktextension.toIntOr
|
||||
import org.mariotaku.ktextension.toLongOr
|
||||
import org.mariotaku.twidere.BuildConfig
|
||||
import org.mariotaku.twidere.Constants.*
|
||||
import org.mariotaku.twidere.TwidereConstants.KEY_MEDIA_PRELOAD
|
||||
import org.mariotaku.twidere.annotation.AccountType
|
||||
import org.mariotaku.twidere.annotation.ImageShapeStyle
|
||||
import org.mariotaku.twidere.annotation.NavbarStyle
|
||||
import org.mariotaku.twidere.annotation.PreviewStyle
|
||||
import org.mariotaku.twidere.annotation.*
|
||||
import org.mariotaku.twidere.extension.getNonEmptyString
|
||||
import org.mariotaku.twidere.model.CustomAPIConfig
|
||||
import org.mariotaku.twidere.model.ProxySettings
|
||||
import org.mariotaku.twidere.model.UserKey
|
||||
import org.mariotaku.twidere.model.account.cred.Credentials
|
||||
import org.mariotaku.twidere.model.timeline.UserTimelineFilter
|
||||
|
@ -255,7 +254,7 @@ object defaultAccountKey : KSimpleKey<UserKey?>(KEY_DEFAULT_ACCOUNT_KEY, null) {
|
|||
}
|
||||
}
|
||||
|
||||
object userTimelineFilterKey : KSimpleKey<UserTimelineFilter>("user_timeline_filter", UserTimelineFilter()) {
|
||||
val userTimelineFilterKey = object : KSimpleKey<UserTimelineFilter>("user_timeline_filter", UserTimelineFilter()) {
|
||||
override fun read(preferences: SharedPreferences): UserTimelineFilter {
|
||||
val rawString = preferences.getString(key, null) ?: return def
|
||||
val options = rawString.split(",")
|
||||
|
@ -278,4 +277,58 @@ object userTimelineFilterKey : KSimpleKey<UserTimelineFilter>("user_timeline_fil
|
|||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val proxyKey = object : KPreferenceKey<ProxySettings?> {
|
||||
override fun contains(preferences: SharedPreferences): Boolean {
|
||||
return read(preferences) != null
|
||||
}
|
||||
|
||||
override fun read(preferences: SharedPreferences): ProxySettings? {
|
||||
when (preferences.getString(KEY_PROXY_TYPE, null)) {
|
||||
ProxyType.HTTP -> {
|
||||
val address = preferences.getString(KEY_PROXY_ADDRESS, null)
|
||||
val host: String
|
||||
val port: Int
|
||||
if (address == null) {
|
||||
host = preferences.getString(KEY_PROXY_HOST, null) ?: return null
|
||||
port = preferences.getString(KEY_PROXY_PORT, null).toIntOr(-1)
|
||||
} else {
|
||||
host = address.substringBefore(':', "")
|
||||
port = address.substringAfter(':', "").toIntOr(-1)
|
||||
}
|
||||
// Simple validation against wrong values
|
||||
if (host.isEmpty() || port !in 0..65535) return null
|
||||
val username = preferences.getString(KEY_PROXY_USERNAME, null)
|
||||
val password = preferences.getString(KEY_PROXY_PASSWORD, null)
|
||||
return ProxySettings.Http(host, port, username, password)
|
||||
}
|
||||
ProxyType.REVERSE -> {
|
||||
val address = preferences.getString(KEY_PROXY_ADDRESS, null) ?:
|
||||
preferences.getString(KEY_PROXY_HOST, null) ?: return null
|
||||
val username = preferences.getString(KEY_PROXY_USERNAME, null)
|
||||
val password = preferences.getString(KEY_PROXY_PASSWORD, null)
|
||||
return ProxySettings.Reverse(address, username, password)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(editor: SharedPreferences.Editor, value: ProxySettings?): Boolean {
|
||||
if (value == null) {
|
||||
remove(editor)
|
||||
} else {
|
||||
value.write(editor)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun remove(editor: SharedPreferences.Editor) {
|
||||
editor.remove(KEY_PROXY_ADDRESS)
|
||||
editor.remove(KEY_PROXY_HOST)
|
||||
editor.remove(KEY_PROXY_PORT)
|
||||
editor.remove(KEY_PROXY_USERNAME)
|
||||
editor.remove(KEY_PROXY_PASSWORD)
|
||||
}
|
||||
|
||||
}
|
|
@ -158,7 +158,7 @@ class ApplicationModule(private val application: Application) {
|
|||
cache: Cache, notifier: PreferenceChangeNotifier): RestHttpClient {
|
||||
val conf = HttpClientFactory.HttpClientConfiguration(prefs)
|
||||
val client = HttpClientFactory.createRestHttpClient(conf, dns, connectionPool, cache)
|
||||
notifier.register(KEY_ENABLE_PROXY, KEY_PROXY_HOST, KEY_PROXY_PORT, KEY_PROXY_TYPE,
|
||||
notifier.register(KEY_ENABLE_PROXY, KEY_PROXY_ADDRESS, KEY_PROXY_TYPE,
|
||||
KEY_PROXY_USERNAME, KEY_PROXY_PASSWORD, KEY_CONNECTION_TIMEOUT,
|
||||
KEY_RETRY_ON_NETWORK_ISSUE) changed@ {
|
||||
if (client !is OkHttpRestClient) return@changed
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.model
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.support.annotation.IntRange
|
||||
import okhttp3.Credentials
|
||||
import okhttp3.OkHttpClient
|
||||
import org.mariotaku.twidere.annotation.ProxyType
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.util.HttpClientFactory
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
|
||||
interface ProxySettings {
|
||||
@ProxyType
|
||||
val type: String
|
||||
|
||||
fun apply(builder: OkHttpClient.Builder)
|
||||
|
||||
fun write(editor: SharedPreferences.Editor)
|
||||
|
||||
class Http(
|
||||
val host: String,
|
||||
@IntRange(from = 0, to = 65535) val port: Int,
|
||||
val username: String?,
|
||||
val password: String?
|
||||
) : ProxySettings {
|
||||
@ProxyType
|
||||
override val type: String = ProxyType.HTTP
|
||||
|
||||
override fun apply(builder: OkHttpClient.Builder) {
|
||||
val address = InetSocketAddress.createUnresolved(host, port)
|
||||
|
||||
builder.proxy(Proxy(Proxy.Type.HTTP, address))
|
||||
|
||||
builder.authenticator { _, response ->
|
||||
val b = response.request().newBuilder()
|
||||
if (response.code() == 407) {
|
||||
if (username != null && password != null) {
|
||||
val credential = Credentials.basic(username, password)
|
||||
b.header("Proxy-Authorization", credential)
|
||||
}
|
||||
}
|
||||
b.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(editor: SharedPreferences.Editor) {
|
||||
editor.putString(KEY_PROXY_ADDRESS, "$host:$port")
|
||||
editor.putString(KEY_PROXY_USERNAME, username)
|
||||
editor.putString(KEY_PROXY_PASSWORD, password)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Reverse(
|
||||
val url: String,
|
||||
val username: String?,
|
||||
val password: String?
|
||||
) : ProxySettings {
|
||||
@ProxyType
|
||||
override val type: String = ProxyType.REVERSE
|
||||
|
||||
override fun apply(builder: OkHttpClient.Builder) {
|
||||
builder.addInterceptor(HttpClientFactory.ReverseProxyInterceptor(url, username, password))
|
||||
}
|
||||
|
||||
override fun write(editor: SharedPreferences.Editor) {
|
||||
editor.putString(KEY_PROXY_TYPE, type)
|
||||
editor.putString(KEY_PROXY_ADDRESS, url)
|
||||
editor.putString(KEY_PROXY_USERNAME, username)
|
||||
editor.putString(KEY_PROXY_PASSWORD, password)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -27,10 +27,7 @@ import android.util.AttributeSet
|
|||
import org.mariotaku.twidere.fragment.preference.ThemedEditTextPreferenceDialogFragmentCompat
|
||||
import org.mariotaku.twidere.preference.iface.IDialogPreference
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/3/15.
|
||||
*/
|
||||
class ThemedEditTextPreference(context: Context, attrs: AttributeSet? = null) :
|
||||
open class ThemedEditTextPreference(context: Context, attrs: AttributeSet? = null) :
|
||||
EditTextPreference(context, attrs), IDialogPreference {
|
||||
|
||||
override fun displayDialog(fragment: PreferenceFragmentCompat) {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.preference.network
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import android.util.AttributeSet
|
||||
import org.mariotaku.ktextension.toIntOr
|
||||
import org.mariotaku.twidere.annotation.ProxyType
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.preference.ThemedEditTextPreference
|
||||
|
||||
class ProxyAddressPreference(context: Context, attrs: AttributeSet?) : ThemedEditTextPreference(context, attrs) {
|
||||
|
||||
override fun onAttachedToHierarchy(preferenceManager: PreferenceManager) {
|
||||
val prefs = preferenceManager.sharedPreferences
|
||||
if (KEY_PROXY_ADDRESS !in prefs) {
|
||||
when (prefs.getString(KEY_PROXY_TYPE, null)) {
|
||||
ProxyType.HTTP -> {
|
||||
val host = prefs.getString(KEY_PROXY_HOST, null)?.takeIf(String::isNotEmpty)
|
||||
val port = prefs.getString(KEY_PROXY_PORT, null).toIntOr(-1)
|
||||
if (host != null && port in 0..65535) {
|
||||
setDefaultValue("$host:$port")
|
||||
}
|
||||
}
|
||||
ProxyType.REVERSE -> {
|
||||
val address = prefs.getString(KEY_PROXY_HOST, null)
|
||||
setDefaultValue(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onAttachedToHierarchy(preferenceManager)
|
||||
}
|
||||
|
||||
override fun setText(text: String?) {
|
||||
super.setText(text)
|
||||
summary = text
|
||||
}
|
||||
}
|
|
@ -8,15 +8,14 @@ import android.util.Log
|
|||
import okhttp3.*
|
||||
import okhttp3.internal.platform.Platform
|
||||
import org.mariotaku.kpreferences.get
|
||||
import org.mariotaku.ktextension.toIntOr
|
||||
import org.mariotaku.restfu.http.RestHttpClient
|
||||
import org.mariotaku.restfu.okhttp3.OkHttpRestClient
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.*
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_CONNECTION_TIMEOUT
|
||||
import org.mariotaku.twidere.constant.SharedPreferenceConstants.KEY_ENABLE_PROXY
|
||||
import org.mariotaku.twidere.constant.cacheSizeLimitKey
|
||||
import org.mariotaku.twidere.constant.proxyKey
|
||||
import org.mariotaku.twidere.util.net.TLSSocketFactory
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
import java.security.KeyStore
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.*
|
||||
|
@ -26,9 +25,6 @@ import javax.net.ssl.TrustManagerFactory
|
|||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 16/1/27.
|
||||
*/
|
||||
object HttpClientFactory {
|
||||
|
||||
fun createRestHttpClient(conf: HttpClientConfiguration, dns: Dns, connectionPool: ConnectionPool,
|
||||
|
@ -201,42 +197,10 @@ object HttpClientFactory {
|
|||
builder.readTimeout(readTimeoutSecs, TimeUnit.SECONDS)
|
||||
}
|
||||
if (prefs.getBoolean(KEY_ENABLE_PROXY, false)) {
|
||||
configProxy(builder)
|
||||
prefs[proxyKey]?.apply(builder)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configProxy(builder: OkHttpClient.Builder) {
|
||||
val proxyType = prefs.getString(KEY_PROXY_TYPE, null) ?: return
|
||||
val proxyHost = prefs.getString(KEY_PROXY_HOST, null)?.takeIf(String::isNotEmpty) ?: return
|
||||
val proxyPort = prefs.getString(KEY_PROXY_PORT, null).toIntOr(-1)
|
||||
val username = prefs.getString(KEY_PROXY_USERNAME, null)?.takeIf(String::isNotEmpty)
|
||||
val password = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeIf(String::isNotEmpty)
|
||||
when (proxyType) {
|
||||
"http" -> {
|
||||
if (proxyPort !in (0..65535)) {
|
||||
return
|
||||
}
|
||||
val address = InetSocketAddress.createUnresolved(proxyHost, proxyPort)
|
||||
builder.proxy(Proxy(Proxy.Type.HTTP, address))
|
||||
|
||||
builder.authenticator { _, response ->
|
||||
val b = response.request().newBuilder()
|
||||
if (response.code() == 407) {
|
||||
if (username != null && password != null) {
|
||||
val credential = Credentials.basic(username, password)
|
||||
b.header("Proxy-Authorization", credential)
|
||||
}
|
||||
}
|
||||
b.build()
|
||||
}
|
||||
}
|
||||
"reverse" -> {
|
||||
builder.addInterceptor(ReverseProxyInterceptor(proxyHost, username, password))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -268,11 +268,11 @@
|
|||
<string name="content">Content</string>
|
||||
<string name="content_and_storage">Content & Storage</string>
|
||||
<string name="content_description_accounts_selector_current">Selected account is <xliff:g id="name">%1$s</xliff:g></string>
|
||||
<string name="content_description_item_status"><xliff:g id="name">%1$s</xliff:g> <xliff:g id="time_with_preposition">%2$s</xliff:g>: <xliff:g id="content">%3$s</xliff:g></string>
|
||||
<string name="content_description_item_status_reply"><xliff:g id="name">%1$s</xliff:g> replying to <xliff:g id="in_reply_to">%2$s</xliff:g> <xliff:g id="time_with_preposition">%3$s</xliff:g>: <xliff:g id="content">%4$s</xliff:g></string>
|
||||
<string name="content_description_item_status_retweet">Retweeted by <xliff:g id="retweeter">%1$s</xliff:g>. <xliff:g id="name">%2$s</xliff:g> <xliff:g id="time_with_preposition">%3$s</xliff:g>: <xliff:g id="content">%4$s</xliff:g></string>
|
||||
<string name="content_description_sticker">Sticker</string>
|
||||
<string name="content_description_item_status"><xliff:g id="name">%1$s</xliff:g><xliff:g id="time_with_preposition">%2$s</xliff:g>: <xliff:g id="content">%3$s</xliff:g></string>
|
||||
<string name="content_description_item_status_reply"><xliff:g id="name">%1$s</xliff:g> replying to <xliff:g id="in_reply_to">%2$s</xliff:g><xliff:g id="time_with_preposition">%3$s</xliff:g>: <xliff:g id="content">%4$s</xliff:g></string>
|
||||
<string name="content_description_item_status_retweet">Retweeted by <xliff:g id="retweeter">%1$s</xliff:g>. <xliff:g id="name">%2$s</xliff:g><xliff:g id="time_with_preposition">%3$s</xliff:g>: <xliff:g id="content">%4$s</xliff:g></string>
|
||||
<string name="content_description_open_user_name_profile">Open <xliff:g id="name">%1$s</xliff:g>\'s profile</string>
|
||||
<string name="content_description_sticker">Sticker</string>
|
||||
<string name="content_to_notify">Content to notify</string>
|
||||
<string name="content_to_refresh">Content to refresh</string>
|
||||
|
||||
|
@ -585,6 +585,7 @@
|
|||
<string name="label_statuses_retweets">Tweets and retweets</string>
|
||||
<string name="label_statuses_retweets_replies">Tweets, retweets and replies</string>
|
||||
<string name="label_streaming_service">Streaming service</string>
|
||||
<string name="label_timeline_style">Timeline style</string>
|
||||
<string name="label_translate_from_language">Translate from <xliff:g id="language">%s</xliff:g></string>
|
||||
<string name="label_translated_to_language">Translated to <xliff:g id="language">%s</xliff:g></string>
|
||||
<string name="label_translation">Translation</string>
|
||||
|
@ -931,6 +932,7 @@
|
|||
<string name="preference_title_notification_ringtone">Ringtone</string>
|
||||
<string name="preference_title_override_language">App language</string>
|
||||
<string name="preference_title_portrait">Portrait</string>
|
||||
<string name="preference_title_proxy_address">Proxy address</string>
|
||||
<string name="preference_title_storage">Storage</string>
|
||||
<string name="preference_title_streaming_content">Streaming content</string>
|
||||
<string name="preference_title_streaming_enabled">Enable streaming</string>
|
||||
|
@ -1214,6 +1216,9 @@
|
|||
<string name="time_source"><xliff:g id="time">%1$s</xliff:g> · <xliff:g id="source">%2$s</xliff:g></string>
|
||||
|
||||
<string name="timeline_streaming_running">Timeline streaming running</string>
|
||||
<string name="timeline_style_gallery">Gallery</string>
|
||||
<string name="timeline_style_normal">Normal</string>
|
||||
<string name="timeline_style_staggered">Staggered</string>
|
||||
<string name="timeline_sync_service">Timeline sync service</string>
|
||||
|
||||
<string name="title_about">About</string>
|
||||
|
@ -1380,8 +1385,4 @@
|
|||
|
||||
<string name="users_blocked">Blocked these users.</string>
|
||||
<string name="users_lists_with_name"><xliff:g id="name">%s</xliff:g>\'s lists</string>
|
||||
<string name="label_timeline_style">Timeline style</string>
|
||||
<string name="timeline_style_normal">Normal</string>
|
||||
<string name="timeline_style_staggered">Staggered</string>
|
||||
<string name="timeline_style_gallery">Gallery</string>
|
||||
</resources>
|
||||
|
|
|
@ -53,17 +53,12 @@
|
|||
android:entryValues="@array/values_proxy_type"
|
||||
android:key="proxy_type"
|
||||
android:title="@string/proxy_type"/>
|
||||
<org.mariotaku.twidere.preference.ThemedEditTextPreference
|
||||
<org.mariotaku.twidere.preference.network.ProxyAddressPreference
|
||||
android:dependency="enable_proxy"
|
||||
android:key="proxy_host"
|
||||
android:inputType="textEmailAddress"
|
||||
android:key="proxy_address"
|
||||
android:singleLine="true"
|
||||
android:title="@string/proxy_host"/>
|
||||
<org.mariotaku.twidere.preference.ThemedEditTextPreference
|
||||
android:dependency="enable_proxy"
|
||||
android:inputType="number"
|
||||
android:key="proxy_port"
|
||||
android:singleLine="true"
|
||||
android:title="@string/proxy_port"/>
|
||||
android:title="@string/preference_title_proxy_address"/>
|
||||
<org.mariotaku.twidere.preference.ThemedEditTextPreference
|
||||
android:dependency="enable_proxy"
|
||||
android:inputType="textEmailAddress"
|
||||
|
|
Loading…
Reference in New Issue