Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81e133b789 |
@@ -2,6 +2,7 @@ plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.21'
|
||||
id "io.sentry.android.gradle" version "3.11.0"
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -107,12 +108,14 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
|
||||
implementation platform('androidx.compose:compose-bom:2022.10.00')
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
|
||||
implementation 'androidx.navigation:navigation-compose:2.6.0'
|
||||
implementation 'androidx.compose.runtime:runtime-livedata'
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
||||
implementation 'androidx.datastore:datastore-preferences:1.0.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Nyms5"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -34,6 +35,20 @@
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- Sentry -->
|
||||
<meta-data
|
||||
android:name="io.sentry.auto-init"
|
||||
android:value="false" />
|
||||
<!-- enable screenshot for crashes -->
|
||||
<meta-data
|
||||
android:name="io.sentry.attach-screenshot"
|
||||
android:value="true" />
|
||||
<!-- enable view hierarchy for crashes -->
|
||||
<meta-data
|
||||
android:name="io.sentry.attach-view-hierarchy"
|
||||
android:value="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
package net.nymtech.nyms5
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import androidx.work.Configuration
|
||||
import androidx.work.DelegatingWorkerFactory
|
||||
import io.sentry.android.core.SentryAndroid
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
val monitoringKey = booleanPreferencesKey("monitoring")
|
||||
|
||||
class App : Application(), Configuration.Provider {
|
||||
companion object {
|
||||
@@ -13,6 +25,30 @@ class App : Application(), Configuration.Provider {
|
||||
|
||||
private val tag = "App"
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val app = this
|
||||
|
||||
runBlocking {
|
||||
val monitoring = applicationContext.dataStore.data.map { preferences ->
|
||||
preferences[monitoringKey] ?: false
|
||||
}.first()
|
||||
|
||||
if (monitoring) {
|
||||
Log.i(tag, "Performance monitoring and error reporting enabled")
|
||||
SentryAndroid.init(app) { options ->
|
||||
options.dsn =
|
||||
"https://6872f5818bc147ef9c0fce114fcaac8a@o967446.ingest.sentry.io/4505306218102784"
|
||||
options.enableAllAutoBreadcrumbs(true)
|
||||
|
||||
// TODO should be adjusted in production env
|
||||
options.tracesSampleRate = 1.0
|
||||
options.profilesSampleRate = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getWorkManagerConfiguration(): Configuration {
|
||||
val workerFactory = DelegatingWorkerFactory()
|
||||
// pass in the NymProxy class instance
|
||||
|
||||
@@ -14,11 +14,21 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
@@ -34,6 +44,8 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import net.nymtech.nyms5.ui.theme.NymTheme
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -42,8 +54,17 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val tag = "MainActivity"
|
||||
@@ -81,18 +102,16 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
Log.d(tag, "____uiState collect")
|
||||
viewModel.uiState.collect {
|
||||
setContent {
|
||||
NymTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Log.d(tag, "____UI recompose")
|
||||
applicationContext.dataStore.data.map { preferences ->
|
||||
preferences[monitoringKey] ?: false
|
||||
}.collect { monitoring ->
|
||||
viewModel.uiState.collect {
|
||||
setContent {
|
||||
NymTheme {
|
||||
val loading = it.loading
|
||||
|
||||
S5ClientSwitch(it.connected, loading, {
|
||||
HomeScreen(it, monitoring, applicationContext.dataStore) {
|
||||
if (!loading) {
|
||||
when {
|
||||
it -> {
|
||||
@@ -106,7 +125,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +140,85 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
proxyState: MainViewModel.ProxyState,
|
||||
monitoring: Boolean,
|
||||
dataStore: DataStore<Preferences>,
|
||||
onSwitch: (value: Boolean) -> Unit,
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Scaffold(topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(stringResource(R.string.app_name))
|
||||
},
|
||||
navigationIcon = {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
if (currentRoute === "proxy") {
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Menu,
|
||||
contentDescription = "Main menu"
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenuItem(onClick = {
|
||||
navController.navigate("monitoring") {
|
||||
popUpTo("proxy")
|
||||
}
|
||||
expanded = false
|
||||
}, text = {
|
||||
Text("Error reporting")
|
||||
})
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = {
|
||||
navController.navigate("proxy") {
|
||||
popUpTo("proxy")
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = "Back home"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}) { contentPadding ->
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = "proxy",
|
||||
modifier = Modifier.padding(contentPadding)
|
||||
) {
|
||||
composable("proxy") {
|
||||
S5ClientSwitch(
|
||||
connected = proxyState.connected,
|
||||
loading = proxyState.loading,
|
||||
onSwitch = onSwitch
|
||||
)
|
||||
}
|
||||
composable("monitoring") {
|
||||
Monitoring(initialValue = monitoring) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
dataStore.edit { settings -> settings[monitoringKey] = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun S5ClientSwitch(
|
||||
@@ -203,6 +301,46 @@ fun S5ClientSwitch(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Monitoring(
|
||||
modifier: Modifier = Modifier,
|
||||
initialValue: Boolean,
|
||||
onSwitch: (value: Boolean) -> Unit,
|
||||
) {
|
||||
var monitoring by remember { mutableStateOf(initialValue) }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text("Enable error reporting")
|
||||
Spacer(modifier = modifier.width(16.dp))
|
||||
Switch(checked = monitoring, onCheckedChange = {
|
||||
monitoring = it
|
||||
onSwitch(it)
|
||||
})
|
||||
}
|
||||
Spacer(modifier = modifier.height(18.dp))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.warning_24),
|
||||
contentDescription = "copy to clipboard",
|
||||
tint = Color.Yellow
|
||||
)
|
||||
Spacer(modifier = modifier.width(16.dp))
|
||||
Text(stringResource(R.string.monitoring_desc_3), color = Color.Yellow)
|
||||
}
|
||||
Spacer(modifier = modifier.height(18.dp))
|
||||
Text(stringResource(R.string.monitoring_desc_1))
|
||||
Spacer(modifier = modifier.height(18.dp))
|
||||
Text(stringResource(R.string.monitoring_desc_2))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewSocks5Client() {
|
||||
@@ -225,3 +363,18 @@ fun PreviewSocks5Client() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewMonitoring() {
|
||||
NymTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Monitoring(initialValue = false) {
|
||||
Log.d("Monitoring", "switch $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class MainViewModel(
|
||||
private val workManager: WorkManager,
|
||||
private val nymProxy: NymProxy
|
||||
) : ViewModel() {
|
||||
private val tag = "viewModel"
|
||||
private val tag = "MainViewModel"
|
||||
|
||||
private val workRequest: OneTimeWorkRequest =
|
||||
OneTimeWorkRequestBuilder<ProxyWorker>()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z"/>
|
||||
</vector>
|
||||
@@ -11,4 +11,8 @@
|
||||
<string name="sp_url">https://harbourmaster.nymtech.net/v1/services?size=100</string>
|
||||
<string name="default_sp">DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh</string>
|
||||
<string name="connected_text">Connected to the mixnet</string>
|
||||
<string name="monitoring_desc_1">Help Nym developers to fix errors, crashes and improve the application by enabling this option. If errors occur or if the app crashes, it will automatically send a report. Also it tracks various performance metrics. We use sentry.io service to handle this.</string>
|
||||
<string name="monitoring_desc_2">Note: A report can include your external IP, this can be useful to catch issues related to IP location.
|
||||
All recorded data is used by Nym developers and for app development purposes only.</string>
|
||||
<string name="monitoring_desc_3">You must restart the application for the change to take effect.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user