Add proxy settings

This commit is contained in:
ceno challenge 2022-11-29 20:24:58 +01:00
parent 9311f90fc9
commit 89c0822158
13 changed files with 222 additions and 17 deletions

View File

@ -15,8 +15,10 @@ import ie.equalit.ouinet.Config
import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.AccountDao
import me.ash.reader.data.model.preference.AccountSettingsProvider import me.ash.reader.data.model.preference.AccountSettingsProvider
import me.ash.reader.data.model.preference.LanguagesPreference import me.ash.reader.data.model.preference.LanguagesPreference
import me.ash.reader.data.model.preference.ProxyPreferences
import me.ash.reader.data.model.preference.SettingsProvider import me.ash.reader.data.model.preference.SettingsProvider
import me.ash.reader.ui.ext.languages import me.ash.reader.ui.ext.languages
import me.ash.reader.ui.ext.proxy
import me.ash.reader.ui.page.common.HomeEntry import me.ash.reader.ui.page.common.HomeEntry
import javax.inject.Inject import javax.inject.Inject
@ -25,7 +27,6 @@ import javax.inject.Inject
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var ouinet: Ouinet
@Inject @Inject
lateinit var imageLoader: ImageLoader lateinit var imageLoader: ImageLoader
@ -44,20 +45,27 @@ class MainActivity : ComponentActivity() {
it.setLocale(this) it.setLocale(this)
} }
// Enable OuiNet client // If a proxy config is set, start ouinet service
val config = Config.ConfigBuilder(this) if (ProxyPreferences.fromValue(proxy) != ProxyPreferences.NoProxy) {
val ouinetBuilder = Config.ConfigBuilder(this)
.setCacheType("bep5-http") .setCacheType("bep5-http")
.setCacheHttpPubKey(BuildConfig.CACHE_PUB_KEY) .setCacheHttpPubKey(BuildConfig.CACHE_PUB_KEY)
.setInjectorCredentials(BuildConfig.INJECTOR_CREDENTIALS) .setInjectorCredentials(BuildConfig.INJECTOR_CREDENTIALS)
.setInjectorTlsCert(BuildConfig.INJECTOR_TLS_CERT) .setInjectorTlsCert(BuildConfig.INJECTOR_TLS_CERT)
.setTlsCaCertStorePath("file:///android_asset/cacert.pem") .setTlsCaCertStorePath("file:///android_asset/cacert.pem")
.setDisableOriginAccess(true) val proxyConfig = when (ProxyPreferences.fromValue(proxy)) {
//.setDisableProxyAccess(true) ProxyPreferences.OuinetBasic -> ouinetBuilder.build()
//.setDisableInjectorAccess(true) ProxyPreferences.OuinetP2P -> {
.build() ouinetBuilder.setDisableOriginAccess(true).build()
}
ouinet = Ouinet(this, config) ProxyPreferences.NoProxy -> {
ouinet.start() // Cannot happen, but when should be exhaustive
ouinetBuilder.build()
}
}
val ouinet = Ouinet(this, proxyConfig)
ouinet.start()
}
setContent { setContent {
CompositionLocalProvider( CompositionLocalProvider(

View File

@ -77,5 +77,8 @@ fun Preferences.toSettings(): Settings {
// Languages // Languages
languages = LanguagesPreference.fromPreferences(this), languages = LanguagesPreference.fromPreferences(this),
// Proxy
proxy = ProxyPreferences.fromPreferences(this),
) )
} }

View File

@ -0,0 +1,60 @@
package me.ash.reader.data.model.preference
import android.content.Context
import me.ash.reader.R
import me.ash.reader.ui.page.settings.accounts.AccountViewModel
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.ash.reader.ui.ext.DataStoreKeys
import me.ash.reader.ui.ext.dataStore
import me.ash.reader.ui.ext.put
sealed class ProxyPreferences(
val value: Int,
) {
object NoProxy : ProxyPreferences(0)
object OuinetBasic : ProxyPreferences(1)
object OuinetP2P : ProxyPreferences(2)
fun put(context: Context, scope: CoroutineScope) {
scope.launch(Dispatchers.IO) {
context.dataStore.put(DataStoreKeys.Proxy, value)
}
}
fun toDesc(context: Context): String =
when (this) {
NoProxy -> context.getString(R.string.proxy_system)
OuinetBasic -> context.getString(R.string.proxy_ouinet_basic)
OuinetP2P -> context.getString(R.string.proxy_ouinet_p2p)
}
companion object {
val default = NoProxy
val values = listOf(
NoProxy,
OuinetBasic,
OuinetP2P,
)
fun fromPreferences(preferences: Preferences): ProxyPreferences =
fromValue(preferences[DataStoreKeys.Proxy.key])
fun fromValue(value: Int?): ProxyPreferences =
when (value) {
0 -> ProxyPreferences.NoProxy
1 -> ProxyPreferences.OuinetBasic
2 -> ProxyPreferences.OuinetP2P
else -> ProxyPreferences.default
}
}
}

View File

@ -76,6 +76,9 @@ data class Settings(
// Languages // Languages
val languages: LanguagesPreference = LanguagesPreference.default, val languages: LanguagesPreference = LanguagesPreference.default,
// Proxy
val proxy: ProxyPreferences = ProxyPreferences.default
) )
// Version // Version
@ -172,6 +175,10 @@ val LocalInitialFilter =
val LocalLanguages = val LocalLanguages =
compositionLocalOf<LanguagesPreference> { LanguagesPreference.default } compositionLocalOf<LanguagesPreference> { LanguagesPreference.default }
// Ouinet settings
val Proxy =
compositionLocalOf<ProxyPreferences> { ProxyPreferences.default }
@Composable @Composable
fun SettingsProvider( fun SettingsProvider(
content: @Composable () -> Unit, content: @Composable () -> Unit,
@ -249,6 +256,9 @@ fun SettingsProvider(
// Languages // Languages
LocalLanguages provides settings.languages, LocalLanguages provides settings.languages,
// Proxy
Proxy provides settings.proxy,
) { ) {
content() content()
} }

View File

@ -19,6 +19,8 @@
*/ */
package me.ash.reader.data.module package me.ash.reader.data.module
import me.ash.reader.data.model.preference.ProxyPreferences
import me.ash.reader.ui.ext.proxy
import android.content.Context import android.content.Context
import dagger.Module import dagger.Module
@ -56,7 +58,8 @@ object OkHttpClientModule {
fun provideOkHttpClient( fun provideOkHttpClient(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): OkHttpClient = cachingHttpClient( ): OkHttpClient = cachingHttpClient(
cacheDirectory = context.cacheDir.resolve("http") cacheDirectory = context.cacheDir.resolve("http"),
proxy = ProxyPreferences.fromValue(context.proxy)
).newBuilder() ).newBuilder()
.addNetworkInterceptor(UserAgentInterceptor) .addNetworkInterceptor(UserAgentInterceptor)
.build() .build()
@ -68,9 +71,16 @@ fun cachingHttpClient(
trustAllCerts: Boolean = true, trustAllCerts: Boolean = true,
connectTimeoutSecs: Long = 30L, connectTimeoutSecs: Long = 30L,
readTimeoutSecs: Long = 30L, readTimeoutSecs: Long = 30L,
proxy: ProxyPreferences
): OkHttpClient { ): OkHttpClient {
val ouinetService = Proxy(Proxy.Type.HTTP, InetSocketAddress("127.0.0.1", 8077))
val builder: OkHttpClient.Builder = OkHttpClient.Builder().proxy(ouinetService) val builder: OkHttpClient.Builder = when(proxy) {
ProxyPreferences.NoProxy -> OkHttpClient.Builder()
ProxyPreferences.OuinetBasic, ProxyPreferences.OuinetP2P -> {
val ouinetService = Proxy(Proxy.Type.HTTP, InetSocketAddress("127.0.0.1", 8077))
OkHttpClient.Builder().proxy(ouinetService)
}
}
if (cacheDirectory != null) { if (cacheDirectory != null) {
builder.cache(Cache(cacheDirectory, cacheSize)) builder.cache(Cache(cacheDirectory, cacheSize))

View File

@ -32,6 +32,9 @@ val Context.initialFilter: Int
val Context.languages: Int val Context.languages: Int
get() = this.dataStore.get(DataStoreKeys.Languages) ?: 0 get() = this.dataStore.get(DataStoreKeys.Languages) ?: 0
val Context.proxy: Int
get() = this.dataStore.get(DataStoreKeys.Proxy) ?: 0
suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) { suspend fun <T> DataStore<Preferences>.put(dataStoreKeys: DataStoreKeys<T>, value: T) {
this.edit { this.edit {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -398,4 +401,10 @@ sealed class DataStoreKeys<T> {
override val key: Preferences.Key<Int> override val key: Preferences.Key<Int>
get() = intPreferencesKey("languages") get() = intPreferencesKey("languages")
} }
// Proxy
object Proxy : DataStoreKeys<Int>() {
override val key: Preferences.Key<Int>
get() = intPreferencesKey("proxy")
}
} }

View File

@ -34,6 +34,7 @@ import me.ash.reader.ui.page.settings.color.flow.FlowPageStylePage
import me.ash.reader.ui.page.settings.color.reading.* import me.ash.reader.ui.page.settings.color.reading.*
import me.ash.reader.ui.page.settings.interaction.InteractionPage import me.ash.reader.ui.page.settings.interaction.InteractionPage
import me.ash.reader.ui.page.settings.languages.LanguagesPage import me.ash.reader.ui.page.settings.languages.LanguagesPage
import me.ash.reader.ui.page.settings.proxy.ProxyPage
import me.ash.reader.ui.page.settings.tips.TipsAndSupportPage import me.ash.reader.ui.page.settings.tips.TipsAndSupportPage
import me.ash.reader.ui.page.startup.StartupPage import me.ash.reader.ui.page.startup.StartupPage
import me.ash.reader.ui.theme.AppTheme import me.ash.reader.ui.theme.AppTheme
@ -198,6 +199,11 @@ fun HomeEntry(
LanguagesPage(navController = navController) LanguagesPage(navController = navController)
} }
// Proxy
animatedComposable(route = RouteName.PROXY) {
ProxyPage(navController = navController)
}
// Tips & Support // Tips & Support
animatedComposable(route = RouteName.TIPS_AND_SUPPORT) { animatedComposable(route = RouteName.TIPS_AND_SUPPORT) {
TipsAndSupportPage(navController) TipsAndSupportPage(navController)

View File

@ -38,4 +38,7 @@ object RouteName {
// Tips & Support // Tips & Support
const val TIPS_AND_SUPPORT = "tips_and_support" const val TIPS_AND_SUPPORT = "tips_and_support"
// Proxy
const val PROXY = "proxy"
} }

View File

@ -142,6 +142,17 @@ fun SettingsPage(
} }
} }
} }
item {
SelectableSettingGroupItem(
title = stringResource(R.string.proxy),
desc = stringResource(R.string.proxy_desc),
icon = Icons.Outlined.SettingsEthernet,
) {
navController.navigate(RouteName.PROXY) {
launchSingleTop = true
}
}
}
item { item {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))

View File

@ -0,0 +1,80 @@
package me.ash.reader.ui.page.settings.proxy
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.KeyboardArrowRight
import androidx.compose.material.icons.outlined.Lightbulb
import androidx.compose.material.icons.rounded.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import me.ash.reader.R
import me.ash.reader.data.model.preference.Proxy
import me.ash.reader.data.model.preference.ProxyPreferences
import me.ash.reader.ui.component.base.Banner
import me.ash.reader.ui.component.base.DisplayText
import me.ash.reader.ui.component.base.FeedbackIconButton
import me.ash.reader.ui.component.base.RYScaffold
import me.ash.reader.ui.page.settings.SettingItem
import me.ash.reader.ui.theme.palette.onLight
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProxyPage(
navController: NavHostController,
) {
val context = LocalContext.current
val proxy = Proxy.current
val scope = rememberCoroutineScope()
RYScaffold(
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
navigationIcon = {
FeedbackIconButton(
imageVector = Icons.Rounded.ArrowBack,
contentDescription = stringResource(R.string.back),
tint = MaterialTheme.colorScheme.onSurface
) {
navController.popBackStack()
}
},
content = {
LazyColumn {
item(key = proxy.value) {
DisplayText(text = stringResource(R.string.proxy), desc = "")
Spacer(modifier = Modifier.height(16.dp))
Spacer(modifier = Modifier.height(16.dp))
}
item {
ProxyPreferences.values.map {
SettingItem(
title = it.toDesc(context),
onClick = {
it.put(context, scope)
},
) {
RadioButton(selected = it == proxy, onClick = {
it.put(context, scope)
})
}
}
}
item {
Spacer(modifier = Modifier.height(24.dp))
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
}
}
}
)
}

View File

@ -117,6 +117,11 @@
<string name="basque" translatable="false">Euskara</string> <string name="basque" translatable="false">Euskara</string>
<string name="indonesian" translatable="false">Bahasa Indonesia</string> <string name="indonesian" translatable="false">Bahasa Indonesia</string>
<string name="chinese_traditional" translatable="false">繁體中文</string> <string name="chinese_traditional" translatable="false">繁體中文</string>
<string name="proxy">Proxy</string>
<string name="proxy_desc">Network access settings (requires restart)</string>
<string name="proxy_system">System settings</string>
<string name="proxy_ouinet_basic">Ouinet (default settings)</string>
<string name="proxy_ouinet_p2p">Ouinet (no direct connection)</string>
<string name="tips_and_support">Tips &amp; support</string> <string name="tips_and_support">Tips &amp; support</string>
<string name="tips_and_support_desc">About, open source licenses</string> <string name="tips_and_support_desc">About, open source licenses</string>
<string name="welcome">Welcome</string> <string name="welcome">Welcome</string>

View File

@ -42,8 +42,8 @@ buildscript {
} }
plugins { plugins {
id 'com.android.application' version '7.1.1' apply false id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.1.1' apply false id 'com.android.library' version '7.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.6.21' apply false id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
} }

View File

@ -1,6 +1,6 @@
#Wed Apr 20 21:38:37 CST 2022 #Tue Nov 29 14:07:40 CET 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME