diff --git a/app/src/main/java/me/ash/reader/MainActivity.kt b/app/src/main/java/me/ash/reader/MainActivity.kt index bf6c398..57cc06b 100644 --- a/app/src/main/java/me/ash/reader/MainActivity.kt +++ b/app/src/main/java/me/ash/reader/MainActivity.kt @@ -15,8 +15,10 @@ import ie.equalit.ouinet.Config import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.model.preference.AccountSettingsProvider 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.ui.ext.languages +import me.ash.reader.ui.ext.proxy import me.ash.reader.ui.page.common.HomeEntry import javax.inject.Inject @@ -25,7 +27,6 @@ import javax.inject.Inject */ @AndroidEntryPoint class MainActivity : ComponentActivity() { - private lateinit var ouinet: Ouinet @Inject lateinit var imageLoader: ImageLoader @@ -44,20 +45,27 @@ class MainActivity : ComponentActivity() { it.setLocale(this) } - // Enable OuiNet client - val config = Config.ConfigBuilder(this) + // If a proxy config is set, start ouinet service + if (ProxyPreferences.fromValue(proxy) != ProxyPreferences.NoProxy) { + val ouinetBuilder = Config.ConfigBuilder(this) .setCacheType("bep5-http") .setCacheHttpPubKey(BuildConfig.CACHE_PUB_KEY) .setInjectorCredentials(BuildConfig.INJECTOR_CREDENTIALS) .setInjectorTlsCert(BuildConfig.INJECTOR_TLS_CERT) .setTlsCaCertStorePath("file:///android_asset/cacert.pem") - .setDisableOriginAccess(true) - //.setDisableProxyAccess(true) - //.setDisableInjectorAccess(true) - .build() - - ouinet = Ouinet(this, config) - ouinet.start() + val proxyConfig = when (ProxyPreferences.fromValue(proxy)) { + ProxyPreferences.OuinetBasic -> ouinetBuilder.build() + ProxyPreferences.OuinetP2P -> { + ouinetBuilder.setDisableOriginAccess(true).build() + } + ProxyPreferences.NoProxy -> { + // Cannot happen, but when should be exhaustive + ouinetBuilder.build() + } + } + val ouinet = Ouinet(this, proxyConfig) + ouinet.start() + } setContent { CompositionLocalProvider( diff --git a/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt index 9eaf6a3..f52d401 100644 --- a/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Preference.kt @@ -77,5 +77,8 @@ fun Preferences.toSettings(): Settings { // Languages languages = LanguagesPreference.fromPreferences(this), + + // Proxy + proxy = ProxyPreferences.fromPreferences(this), ) } diff --git a/app/src/main/java/me/ash/reader/data/model/preference/ProxyPreferences.kt b/app/src/main/java/me/ash/reader/data/model/preference/ProxyPreferences.kt new file mode 100644 index 0000000..d9673db --- /dev/null +++ b/app/src/main/java/me/ash/reader/data/model/preference/ProxyPreferences.kt @@ -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 + } + } +} + diff --git a/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt index b8305a7..06c03b3 100644 --- a/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt +++ b/app/src/main/java/me/ash/reader/data/model/preference/Settings.kt @@ -76,6 +76,9 @@ data class Settings( // Languages val languages: LanguagesPreference = LanguagesPreference.default, + + // Proxy + val proxy: ProxyPreferences = ProxyPreferences.default ) // Version @@ -172,6 +175,10 @@ val LocalInitialFilter = val LocalLanguages = compositionLocalOf { LanguagesPreference.default } +// Ouinet settings +val Proxy = + compositionLocalOf { ProxyPreferences.default } + @Composable fun SettingsProvider( content: @Composable () -> Unit, @@ -249,6 +256,9 @@ fun SettingsProvider( // Languages LocalLanguages provides settings.languages, + + // Proxy + Proxy provides settings.proxy, ) { content() } diff --git a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt index 72ff5cc..4ad0d04 100644 --- a/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt +++ b/app/src/main/java/me/ash/reader/data/module/OkHttpClientModule.kt @@ -19,6 +19,8 @@ */ 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 dagger.Module @@ -56,7 +58,8 @@ object OkHttpClientModule { fun provideOkHttpClient( @ApplicationContext context: Context, ): OkHttpClient = cachingHttpClient( - cacheDirectory = context.cacheDir.resolve("http") + cacheDirectory = context.cacheDir.resolve("http"), + proxy = ProxyPreferences.fromValue(context.proxy) ).newBuilder() .addNetworkInterceptor(UserAgentInterceptor) .build() @@ -68,9 +71,16 @@ fun cachingHttpClient( trustAllCerts: Boolean = true, connectTimeoutSecs: Long = 30L, readTimeoutSecs: Long = 30L, + proxy: ProxyPreferences ): 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) { builder.cache(Cache(cacheDirectory, cacheSize)) diff --git a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt index c62c0b3..41da146 100644 --- a/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt +++ b/app/src/main/java/me/ash/reader/ui/ext/DataStoreExt.kt @@ -32,6 +32,9 @@ val Context.initialFilter: Int val Context.languages: Int get() = this.dataStore.get(DataStoreKeys.Languages) ?: 0 +val Context.proxy: Int + get() = this.dataStore.get(DataStoreKeys.Proxy) ?: 0 + suspend fun DataStore.put(dataStoreKeys: DataStoreKeys, value: T) { this.edit { withContext(Dispatchers.IO) { @@ -398,4 +401,10 @@ sealed class DataStoreKeys { override val key: Preferences.Key get() = intPreferencesKey("languages") } + + // Proxy + object Proxy : DataStoreKeys() { + override val key: Preferences.Key + get() = intPreferencesKey("proxy") + } } diff --git a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt index 1ff2518..d980df4 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/HomeEntry.kt @@ -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.interaction.InteractionPage 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.startup.StartupPage import me.ash.reader.ui.theme.AppTheme @@ -198,6 +199,11 @@ fun HomeEntry( LanguagesPage(navController = navController) } + // Proxy + animatedComposable(route = RouteName.PROXY) { + ProxyPage(navController = navController) + } + // Tips & Support animatedComposable(route = RouteName.TIPS_AND_SUPPORT) { TipsAndSupportPage(navController) diff --git a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt index f728842..83a76e6 100644 --- a/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt +++ b/app/src/main/java/me/ash/reader/ui/page/common/RouteName.kt @@ -38,4 +38,7 @@ object RouteName { // Tips & Support const val TIPS_AND_SUPPORT = "tips_and_support" + + // Proxy + const val PROXY = "proxy" } diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt index e85ea07..0c66320 100644 --- a/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/settings/SettingsPage.kt @@ -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 { Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars)) diff --git a/app/src/main/java/me/ash/reader/ui/page/settings/proxy/ProxyPage.kt b/app/src/main/java/me/ash/reader/ui/page/settings/proxy/ProxyPage.kt new file mode 100644 index 0000000..8e4a15a --- /dev/null +++ b/app/src/main/java/me/ash/reader/ui/page/settings/proxy/ProxyPage.kt @@ -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)) + } + } + } + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8896650..b3d9086 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,6 +117,11 @@ Euskara Bahasa Indonesia 繁體中文 + Proxy + Network access settings (requires restart) + System settings + Ouinet (default settings) + Ouinet (no direct connection) Tips & support About, open source licenses Welcome diff --git a/build.gradle b/build.gradle index 3cea2b7..28a0c6f 100644 --- a/build.gradle +++ b/build.gradle @@ -42,8 +42,8 @@ buildscript { } plugins { - id 'com.android.application' version '7.1.1' apply false - id 'com.android.library' version '7.1.1' apply false + id 'com.android.application' version '7.3.1' apply false + id 'com.android.library' version '7.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.6.21' apply false } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f414ae..61bef73 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 20 21:38:37 CST 2022 +#Tue Nov 29 14:07:40 CET 2022 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 zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME