refactor state

This commit is contained in:
Zane Schepke 2023-12-25 23:12:06 -05:00
parent 408d88390b
commit a3386552d5
38 changed files with 364 additions and 341 deletions

View File

@ -3,7 +3,7 @@ package com.zaneschepke.wireguardautotunnel
import androidx.room.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import java.io.IOException
import org.junit.Rule
import org.junit.Test

View File

@ -29,3 +29,4 @@ fun BigDecimal.toThreeDecimalPlaceString(): String {
val df = DecimalFormat("#.###")
return df.format(this)
}

View File

@ -5,9 +5,9 @@ import android.content.Context
import android.content.pm.PackageManager
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import dagger.hilt.android.HiltAndroidApp
import java.io.IOException
import javax.inject.Inject
@ -17,7 +17,7 @@ import timber.log.Timber
@HiltAndroidApp
class WireGuardAutoTunnel : Application() {
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
@Inject
lateinit var dataStoreManager: DataStoreManager

View File

@ -1,11 +1,11 @@
package com.zaneschepke.wireguardautotunnel.repository
package com.zaneschepke.wireguardautotunnel.data
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
@Database(
entities = [Settings::class, TunnelConfig::class],
@ -20,7 +20,7 @@ import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
)
@TypeConverters(DatabaseListConverters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun settingDao(): SettingsDoa
abstract fun settingDao(): SettingsDao
abstract fun tunnelConfigDoa(): TunnelConfigDao
}

View File

@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.repository
package com.zaneschepke.wireguardautotunnel.data
import androidx.room.TypeConverter
import kotlinx.serialization.encodeToString

View File

@ -1,15 +1,15 @@
package com.zaneschepke.wireguardautotunnel.repository
package com.zaneschepke.wireguardautotunnel.data
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import kotlinx.coroutines.flow.Flow
@Dao
interface SettingsDoa {
interface SettingsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(t: Settings)

View File

@ -1,11 +1,11 @@
package com.zaneschepke.wireguardautotunnel.repository
package com.zaneschepke.wireguardautotunnel.data
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import kotlinx.coroutines.flow.Flow
@Dao

View File

@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.repository.datastore
package com.zaneschepke.wireguardautotunnel.data.datastore
import android.content.Context
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey

View File

@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.repository.model
package com.zaneschepke.wireguardautotunnel.data.model
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.repository.model
package com.zaneschepke.wireguardautotunnel.data.model
import androidx.room.ColumnInfo
import androidx.room.Entity

View File

@ -0,0 +1,11 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
suspend fun save(settings : Settings)
fun getSettings() : Flow<Settings>
suspend fun getAll() : List<Settings>
}

View File

@ -0,0 +1,20 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import kotlinx.coroutines.flow.Flow
class SettingsRepositoryImpl(private val settingsDoa: SettingsDao) : SettingsRepository {
override suspend fun save(settings: Settings) {
settingsDoa.save(settings)
}
override fun getSettings(): Flow<Settings> {
return settingsDoa.getSettingsFlow()
}
override suspend fun getAll(): List<Settings> {
return settingsDoa.getAll()
}
}

View File

@ -0,0 +1,5 @@
package com.zaneschepke.wireguardautotunnel.data.repository
interface TunnelConfigRepository {
}

View File

@ -0,0 +1,7 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
class TunnelConfigRepositoryImpl(private val tunnelConfigDao: TunnelConfigDao) : TunnelConfigRepository {
}

View File

@ -3,7 +3,7 @@ package com.zaneschepke.wireguardautotunnel.module
import android.content.Context
import androidx.room.Room
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

View File

@ -1,10 +1,14 @@
package com.zaneschepke.wireguardautotunnel.module
import android.content.Context
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepositoryImpl
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository
import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepositoryImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -17,16 +21,28 @@ import javax.inject.Singleton
class RepositoryModule {
@Singleton
@Provides
fun provideSettingsRepository(appDatabase: AppDatabase): SettingsDoa {
fun provideSettingsDoa(appDatabase: AppDatabase): SettingsDao {
return appDatabase.settingDao()
}
@Singleton
@Provides
fun provideTunnelConfigRepository(appDatabase: AppDatabase): TunnelConfigDao {
fun provideTunnelConfigDoa(appDatabase: AppDatabase): TunnelConfigDao {
return appDatabase.tunnelConfigDoa()
}
@Singleton
@Provides
fun provideTunnelConfigRepository(tunnelConfigDao: TunnelConfigDao): TunnelConfigRepository {
return TunnelConfigRepositoryImpl(tunnelConfigDao)
}
@Singleton
@Provides
fun provideSettingsRepository(settingsDao: SettingsDao): SettingsRepository {
return SettingsRepositoryImpl(settingsDao)
}
@Singleton
@Provides
fun providePreferencesDataStore(@ApplicationContext context: Context): DataStoreManager {

View File

@ -6,7 +6,7 @@ import com.wireguard.android.backend.GoBackend
import com.wireguard.android.backend.WgQuickBackend
import com.wireguard.android.util.RootShell
import com.wireguard.android.util.ToolsInstaller
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
import dagger.Module
@ -51,7 +51,7 @@ class TunnelModule {
fun provideVpnService(
@Userspace userspaceBackend: Backend,
@Kernel kernelBackend: Backend,
settingsDoa: SettingsDoa
settingsDoa: SettingsDao
): VpnService {
return WireGuardTunnel(userspaceBackend, kernelBackend, settingsDoa)
}

View File

@ -4,7 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.zaneschepke.wireguardautotunnel.goAsync
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -13,7 +13,7 @@ import kotlinx.coroutines.cancel
@AndroidEntryPoint
class BootReceiver : BroadcastReceiver() {
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
override fun onReceive(
context: Context,

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.goAsync
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -15,7 +15,7 @@ import kotlinx.coroutines.delay
@AndroidEntryPoint
class NotificationActionReceiver : BroadcastReceiver() {
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
override fun onReceive(
context: Context,

View File

@ -12,8 +12,8 @@ import androidx.lifecycle.lifecycleScope
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
@ -44,7 +44,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
lateinit var ethernetService: NetworkService<EthernetService>
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
@Inject
lateinit var notificationService: NotificationService

View File

@ -8,8 +8,8 @@ import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
@ -28,7 +28,7 @@ class WireGuardTunnelService : ForegroundService() {
lateinit var vpnService: VpnService
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
@Inject
lateinit var notificationService: NotificationService

View File

@ -3,10 +3,10 @@ package com.zaneschepke.wireguardautotunnel.service.shortcut
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
@ -20,7 +20,7 @@ import timber.log.Timber
@AndroidEntryPoint
class ShortcutsActivity : ComponentActivity() {
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
@Inject
lateinit var tunnelConfigRepo: TunnelConfigDao

View File

@ -5,9 +5,9 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import dagger.hilt.android.AndroidEntryPoint
@ -22,7 +22,7 @@ import timber.log.Timber
@AndroidEntryPoint
class TunnelControlTile : TileService() {
@Inject
lateinit var settingsRepo: SettingsDoa
lateinit var settingsRepo: SettingsDao
@Inject
lateinit var configRepo: TunnelConfigDao

View File

@ -3,7 +3,7 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import kotlinx.coroutines.flow.SharedFlow
interface VpnService : Tunnel {

View File

@ -9,8 +9,8 @@ import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.module.Kernel
import com.zaneschepke.wireguardautotunnel.module.Userspace
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@ -31,7 +31,7 @@ class WireGuardTunnel
constructor(
@Userspace private val userspaceBackend: Backend,
@Kernel private val kernelBackend: Backend,
private val settingsRepo: SettingsDoa
private val settingsRepo: SettingsDao
) : VpnService {
private val _tunnelName = MutableStateFlow("")
override val tunnelName get() = _tunnelName.asStateFlow()

View File

@ -1,17 +1,13 @@
package com.zaneschepke.wireguardautotunnel.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel
class ActivityViewModel @Inject constructor(
private val settingsRepo: SettingsDoa,
private val settingsRepo: SettingsDao,
) : ViewModel() {
// val settings = settingsRepo.getSettingsFlow().stateIn(viewModelScope,
// SharingStarted.WhileSubscribed(5000L), Settings()

View File

@ -15,9 +15,9 @@ import com.wireguard.config.Peer
import com.wireguard.crypto.Key
import com.wireguard.crypto.KeyPair
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.models.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.models.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
@ -35,7 +35,7 @@ class ConfigViewModel
constructor(
private val application: Application,
private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa
private val settingsRepo: SettingsDao
) : ViewModel() {
private val _tunnel = MutableStateFlow<TunnelConfig?>(null)
private val _tunnelName = MutableStateFlow("")

View File

@ -84,9 +84,8 @@ import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.ui.ActivityViewModel
import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem

View File

@ -10,10 +10,11 @@ import androidx.lifecycle.viewModelScope
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
@ -39,7 +40,7 @@ class MainViewModel
constructor(
private val application: Application,
private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa,
private val settingsRepository: SettingsRepository,
private val vpnService: VpnService
) : ViewModel() {
val tunnels get() = tunnelRepo.getAllFlow()
@ -53,10 +54,9 @@ constructor(
init {
viewModelScope.launch(Dispatchers.IO) {
settingsRepo.getAllFlow().filter { it.isNotEmpty() }.collect {
val settings = it.first()
validateWatcherServiceState(settings)
_settings.emit(settings)
settingsRepository.getSettings().collect {
validateWatcherServiceState(it)
_settings.emit(it)
}
}
}
@ -79,13 +79,13 @@ constructor(
viewModelScope.launch {
if (tunnelRepo.count() == 1L) {
ServiceManager.stopWatcherService(application.applicationContext)
val settings = settingsRepo.getAll()
val settings = settingsRepository.getAll()
if (settings.isNotEmpty()) {
val setting = settings[0]
setting.defaultTunnel = null
setting.isAutoTunnelEnabled = false
setting.isAlwaysOnVpnEnabled = false
settingsRepo.save(setting)
saveSettings(setting)
}
}
tunnelRepo.delete(tunnel)
@ -237,6 +237,11 @@ constructor(
}
}
private suspend fun saveSettings(settings: Settings) {
//TODO handle error if fails
settingsRepository.save(settings)
}
private fun getFileName(
context: Context,
uri: Uri
@ -268,7 +273,7 @@ constructor(
defaultTunnel = selectedTunnel.toString()
)
)
settingsRepo.save(_settings.value)
settingsRepository.save(_settings.value)
}
}
}

View File

@ -4,7 +4,7 @@ import android.Manifest
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -38,13 +38,11 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
@ -72,17 +70,15 @@ import com.wireguard.android.backend.Tunnel
import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.ui.ActivityViewModel
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@OptIn(
ExperimentalPermissionsApi::class,
ExperimentalLayoutApi::class,
@ -102,33 +98,26 @@ fun SettingsScreen(
val keyboardController = LocalSoftwareKeyboardController.current
val interactionSource = remember { MutableInteractionSource() }
val settings by viewModel.settings.collectAsStateWithLifecycle()
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle()
val vpnState = viewModel.vpnState.collectAsStateWithLifecycle()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") }
var isBackgroundLocationGranted by remember { mutableStateOf(true) }
var didExportFiles by remember { mutableStateOf(false) }
var showAuthPrompt by remember { mutableStateOf(false) }
var isLocationDisclosureShown by rememberSaveable {
mutableStateOf(false)
}
val screenPadding = 5.dp
val fillMaxWidth = .85f
LaunchedEffect(Unit) {
isLocationDisclosureShown = viewModel.isLocationDisclosureShown()
}
//TODO add error collecting and displaying for WGTunnelErrors
fun exportAllConfigs() {
try {
val files = tunnels.map { File(context.cacheDir, "${it.name}.conf") }
val files = uiState.tunnels.map { File(context.cacheDir, "${it.name}.conf") }
files.forEachIndexed { index, file ->
file.outputStream().use {
it.write(tunnels[index].wgQuick.toByteArray())
it.write(uiState.tunnels[index].wgQuick.toByteArray())
}
}
FileUtils.saveFilesToZip(context, files)
@ -163,20 +152,31 @@ fun SettingsScreen(
fun openSettings() {
scope.launch {
val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data =
Uri.fromParts("package", context.packageName, null)
context.startActivity(intentSettings)
}
}
fun checkFineLocationGranted() {
isBackgroundLocationGranted = if (!fineLocationState.status.isGranted) {
false
} else {
scope.launch {
viewModel.setLocationDisclosureShown()
}
true
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
isBackgroundLocationGranted = if (!backgroundLocationState.status.isGranted) {
false
} else {
if(!isLocationDisclosureShown) {
SideEffect {
viewModel.setLocationDisclosureShown()
}
true
@ -194,7 +194,7 @@ fun SettingsScreen(
}
}
AnimatedVisibility(!isLocationDisclosureShown) {
AnimatedVisibility(!uiState.isLocationDisclosureShown) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
@ -270,7 +270,7 @@ fun SettingsScreen(
)
}
if (tunnels.isEmpty()) {
if (uiState.tunnels.isEmpty()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@ -330,17 +330,15 @@ fun SettingsScreen(
)
ConfigurationToggle(
stringResource(id = R.string.tunnel_on_wifi),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnWifiEnabled,
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = uiState.settings.isTunnelOnWifiEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
viewModel.onToggleTunnelOnWifi()
}
},
modifier = Modifier.focusRequester(focusRequester)
)
AnimatedVisibility(visible = settings.isTunnelOnWifiEnabled) {
AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
Column {
FlowRow(
modifier = Modifier
@ -348,19 +346,17 @@ fun SettingsScreen(
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(5.dp)
) {
settings.trustedNetworkSSIDs.forEach { ssid ->
uiState.settings.trustedNetworkSSIDs.forEach { ssid ->
ClickableIconButton(
onIconClick = {
scope.launch {
viewModel.onDeleteTrustedSSID(ssid)
}
},
text = ssid,
icon = Icons.Filled.Close,
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled)
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled)
)
}
if (settings.trustedNetworkSSIDs.isEmpty()) {
if (uiState.settings.trustedNetworkSSIDs.isEmpty()) {
Text(
stringResource(R.string.none),
fontStyle = FontStyle.Italic,
@ -369,7 +365,7 @@ fun SettingsScreen(
}
}
OutlinedTextField(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
value = currentText,
onValueChange = { currentText = it },
label = { Text(stringResource(R.string.add_trusted_ssid)) },
@ -418,36 +414,30 @@ fun SettingsScreen(
}
ConfigurationToggle(
stringResource(R.string.tunnel_mobile_data),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnMobileDataEnabled,
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = uiState.settings.isTunnelOnMobileDataEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
viewModel.onToggleTunnelOnMobileData()
}
}
)
ConfigurationToggle(
stringResource(id = R.string.tunnel_on_ethernet),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnEthernetEnabled,
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = uiState.settings.isTunnelOnEthernetEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
viewModel.onToggleTunnelOnEthernet()
}
}
)
ConfigurationToggle(
stringResource(R.string.battery_saver),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled),
checked = settings.isBatterySaverEnabled,
enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = uiState.settings.isBatterySaverEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
viewModel.onToggleBatterySaver()
}
}
)
Row(
verticalAlignment = Alignment.CenterVertically,
@ -458,9 +448,9 @@ fun SettingsScreen(
horizontalArrangement = Arrangement.Center
) {
TextButton(
enabled = !settings.isAlwaysOnVpnEnabled,
enabled = !uiState.settings.isAlwaysOnVpnEnabled,
onClick = {
if (!isAllAutoTunnelPermissionsEnabled() && settings.isTunnelOnWifiEnabled) {
if (!isAllAutoTunnelPermissionsEnabled() && uiState.settings.isTunnelOnWifiEnabled) {
val message =
if (!isBackgroundLocationGranted) {
context.getString(R.string.background_location_required)
@ -471,14 +461,12 @@ fun SettingsScreen(
}
showSnackbarMessage(message)
} else {
scope.launch {
viewModel.toggleAutoTunnel()
}
}
}
) {
val autoTunnelButtonText =
if (settings.isAutoTunnelEnabled) {
if (uiState.settings.isAutoTunnelEnabled) {
stringResource(R.string.disable_auto_tunnel)
} else {
stringResource(id = R.string.enable_auto_tunnel)
@ -510,19 +498,13 @@ fun SettingsScreen(
ConfigurationToggle(
stringResource(R.string.use_kernel),
enabled = !(
settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled ||
(vpnState.value == Tunnel.State.UP)
uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled ||
(uiState.tunnelState == Tunnel.State.UP)
),
checked = settings.isKernelEnabled,
checked = uiState.settings.isKernelEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
try {
viewModel.onToggleKernelMode()
} catch (e: WgTunnelException) {
showSnackbarMessage(e.message)
}
}
}
)
}
@ -550,8 +532,8 @@ fun SettingsScreen(
)
ConfigurationToggle(
stringResource(R.string.always_on_vpn_support),
enabled = !settings.isAutoTunnelEnabled,
checked = settings.isAlwaysOnVpnEnabled,
enabled = !uiState.settings.isAutoTunnelEnabled,
checked = uiState.settings.isAlwaysOnVpnEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {
@ -562,7 +544,7 @@ fun SettingsScreen(
ConfigurationToggle(
stringResource(R.string.enabled_app_shortcuts),
enabled = true,
checked = settings.isShortcutsEnabled,
checked = uiState.settings.isShortcutsEnabled,
padding = screenPadding,
onCheckChanged = {
scope.launch {

View File

@ -0,0 +1,13 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
data class SettingsUiState(
val settings : Settings = Settings(),
val tunnels : List<TunnelConfig> = emptyList(),
val tunnelState : Tunnel.State = Tunnel.State.DOWN,
val isLocationDisclosureShown : Boolean = true,
val loading : Boolean = true,
)

View File

@ -6,18 +6,18 @@ import android.location.LocationManager
import android.os.Build
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import timber.log.Timber
@ -28,24 +28,27 @@ class SettingsViewModel
constructor(
private val application: Application,
private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa,
private val settingsRepository: SettingsRepository,
private val dataStoreManager: DataStoreManager,
private val rootShell: RootShell,
private val vpnService: VpnService
) : ViewModel() {
val settings = settingsRepo.getSettingsFlow().stateIn(viewModelScope,
SharingStarted.WhileSubscribed(5_000L), Settings())
val tunnels = tunnelRepo.getAllFlow().stateIn(viewModelScope,
SharingStarted.WhileSubscribed(5_000L), emptyList())
val vpnState get() = vpnService.state.stateIn(viewModelScope,
SharingStarted.WhileSubscribed(5_000L), Tunnel.State.DOWN)
val uiState = combine(
settingsRepository.getSettings(),
tunnelRepo.getAllFlow(),
vpnService.state,
dataStoreManager.locationDisclosureFlow,
){ settings, tunnels, tunnelState, locationDisclosure ->
SettingsUiState(settings, tunnels, tunnelState, locationDisclosure ?: false, false)
}.stateIn(viewModelScope,
SharingStarted.WhileSubscribed(5_000L), SettingsUiState())
suspend fun onSaveTrustedSSID(ssid: String) {
fun onSaveTrustedSSID(ssid: String) {
val trimmed = ssid.trim()
if (!settings.value.trustedNetworkSSIDs.contains(trimmed)) {
settings.value.trustedNetworkSSIDs.add(trimmed)
settingsRepo.save(settings.value)
if (!uiState.value.settings.trustedNetworkSSIDs.contains(trimmed)) {
uiState.value.settings.trustedNetworkSSIDs.add(trimmed)
saveSettings(uiState.value.settings)
} else {
throw WgTunnelException("SSID already exists.")
}
@ -55,39 +58,37 @@ constructor(
return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) ?: false
}
fun setLocationDisclosureShown() {
viewModelScope.launch {
fun setLocationDisclosureShown() = viewModelScope.launch {
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, true)
}
}
suspend fun onToggleTunnelOnMobileData() {
settingsRepo.save(
settings.value.copy(
isTunnelOnMobileDataEnabled = !settings.value.isTunnelOnMobileDataEnabled
fun onToggleTunnelOnMobileData() {
saveSettings(
uiState.value.settings.copy(
isTunnelOnMobileDataEnabled = !uiState.value.settings.isTunnelOnMobileDataEnabled
)
)
}
suspend fun onDeleteTrustedSSID(ssid: String) {
settings.value.trustedNetworkSSIDs.remove(ssid)
settingsRepo.save(settings.value)
fun onDeleteTrustedSSID(ssid: String) {
uiState.value.settings.trustedNetworkSSIDs.remove(ssid)
saveSettings(uiState.value.settings)
}
private suspend fun getDefaultTunnelOrFirst() : String {
return settings.value.defaultTunnel ?: tunnelRepo.getAll().first().wgQuick
return uiState.value.settings.defaultTunnel ?: tunnelRepo.getAll().first().wgQuick
}
suspend fun toggleAutoTunnel() {
fun toggleAutoTunnel() = viewModelScope.launch {
val defaultTunnel = getDefaultTunnelOrFirst()
if (settings.value.isAutoTunnelEnabled) {
if (uiState.value.settings.isAutoTunnelEnabled) {
ServiceManager.stopWatcherService(application)
} else {
ServiceManager.startWatcherService(application, defaultTunnel)
}
saveSettings(
settings.value.copy(
isAutoTunnelEnabled = settings.value.isAutoTunnelEnabled,
uiState.value.settings.copy(
isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled,
defaultTunnel = defaultTunnel
)
)
@ -95,20 +96,20 @@ constructor(
suspend fun onToggleAlwaysOnVPN() {
val updatedSettings =
settings.value.copy(
isAlwaysOnVpnEnabled = !settings.value.isAlwaysOnVpnEnabled,
uiState.value.settings.copy(
isAlwaysOnVpnEnabled = !uiState.value.settings.isAlwaysOnVpnEnabled,
defaultTunnel = getDefaultTunnelOrFirst()
)
saveSettings(updatedSettings)
}
private suspend fun saveSettings(settings: Settings) {
settingsRepo.save(settings)
private fun saveSettings(settings: Settings) = viewModelScope.launch {
settingsRepository.save(settings)
}
suspend fun onToggleTunnelOnEthernet() {
saveSettings(settings.value.copy(
isTunnelOnEthernetEnabled = !settings.value.isTunnelOnEthernetEnabled
fun onToggleTunnelOnEthernet() {
saveSettings(uiState.value.settings.copy(
isTunnelOnEthernetEnabled = !uiState.value.settings.isTunnelOnEthernetEnabled
))
}
@ -122,40 +123,40 @@ constructor(
return (!isLocationServicesEnabled() && Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
}
suspend fun onToggleShortcutsEnabled() {
fun onToggleShortcutsEnabled() {
saveSettings(
settings.value.copy(
isShortcutsEnabled = !settings.value.isShortcutsEnabled
uiState.value.settings.copy(
isShortcutsEnabled = !uiState.value.settings.isShortcutsEnabled
)
)
}
suspend fun onToggleBatterySaver() {
fun onToggleBatterySaver() {
saveSettings(
settings.value.copy(
isBatterySaverEnabled = !settings.value.isBatterySaverEnabled
uiState.value.settings.copy(
isBatterySaverEnabled = !uiState.value.settings.isBatterySaverEnabled
)
)
}
private suspend fun saveKernelMode(on: Boolean) {
private fun saveKernelMode(on: Boolean) {
saveSettings(
settings.value.copy(
uiState.value.settings.copy(
isKernelEnabled = on
)
)
}
suspend fun onToggleTunnelOnWifi() {
fun onToggleTunnelOnWifi() {
saveSettings(
settings.value.copy(
isTunnelOnWifiEnabled = !settings.value.isTunnelOnWifiEnabled
uiState.value.settings.copy(
isTunnelOnWifiEnabled = !uiState.value.settings.isTunnelOnWifiEnabled
)
)
}
suspend fun onToggleKernelMode() {
if (!settings.value.isKernelEnabled) {
fun onToggleKernelMode() = viewModelScope.launch {
if (!uiState.value.settings.isKernelEnabled) {
try {
rootShell.start()
Timber.d("Root shell accepted!")

View File

@ -51,11 +51,10 @@ import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsViewModel
@Composable
fun SupportScreen(
viewModel: SettingsViewModel = hiltViewModel(),
viewModel: SupportViewModel = hiltViewModel(),
padding: PaddingValues,
focusRequester: FocusRequester
) {
@ -77,70 +76,55 @@ fun SupportScreen(
putExtra(Intent.EXTRA_EMAIL, context.getString(R.string.my_email))
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject))
}
startActivity(
context,
createChooser(intent, context.getString(R.string.email_chooser)),
null
)
startActivity(context, createChooser(intent, context.getString(R.string.email_chooser)), null)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top,
modifier =
Modifier
.fillMaxSize()
Modifier.fillMaxSize()
.verticalScroll(rememberScrollState())
.focusable()
.padding(padding)
) {
.padding(padding)) {
Surface(
tonalElevation = 2.dp,
shadowElevation = 2.dp,
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface,
modifier =
(
if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) {
Modifier
.height(IntrinsicSize.Min)
(if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) {
Modifier.height(IntrinsicSize.Min)
.fillMaxWidth(fillMaxWidth)
.padding(top = 10.dp)
} else {
Modifier
.fillMaxWidth(fillMaxWidth)
.padding(top = 20.dp)
}
).padding(bottom = 25.dp)
) {
Modifier.fillMaxWidth(fillMaxWidth).padding(top = 20.dp)
})
.padding(bottom = 25.dp)) {
Column(modifier = Modifier.padding(20.dp)) {
Text(
stringResource(R.string.thank_you),
textAlign = TextAlign.Start,
modifier = Modifier.padding(bottom = 20.dp),
fontSize = 16.sp
)
fontSize = 16.sp)
Text(
stringResource(id = R.string.support_help_text),
textAlign = TextAlign.Start,
fontSize = 16.sp,
modifier = Modifier.padding(bottom = 20.dp)
)
TextButton(onClick = {
openWebPage(context.resources.getString(R.string.docs_url))
}, modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester)) {
modifier = Modifier.padding(bottom = 20.dp))
TextButton(
onClick = { openWebPage(context.resources.getString(R.string.docs_url)) },
modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
modifier = Modifier.fillMaxWidth()) {
Row {
Icon(Icons.Rounded.Book, stringResource(id = R.string.docs))
Text(
stringResource(id = R.string.docs_description),
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
modifier = Modifier.padding(start = 10.dp))
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
}
@ -148,26 +132,20 @@ fun SupportScreen(
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton(
onClick = { openWebPage(context.resources.getString(R.string.discord_url)) },
modifier = Modifier.padding(vertical = 5.dp)
) {
modifier = Modifier.padding(vertical = 5.dp)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
modifier = Modifier.fillMaxWidth()) {
Row {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.discord),
stringResource(
id = R.string.discord
),
Modifier.size(25.dp)
)
stringResource(id = R.string.discord),
Modifier.size(25.dp))
Text(
stringResource(id = R.string.discord_description),
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
modifier = Modifier.padding(start = 10.dp))
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
}
@ -175,47 +153,37 @@ fun SupportScreen(
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton(
onClick = { openWebPage(context.resources.getString(R.string.github_url)) },
modifier = Modifier.padding(vertical = 5.dp)
) {
modifier = Modifier.padding(vertical = 5.dp)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
modifier = Modifier.fillMaxWidth()) {
Row {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.github),
stringResource(
id = R.string.github
),
Modifier.size(25.dp)
)
stringResource(id = R.string.github),
Modifier.size(25.dp))
Text(
"Open an issue",
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
modifier = Modifier.padding(start = 10.dp))
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
}
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton(
onClick = { launchEmail() },
modifier = Modifier.padding(vertical = 5.dp)
) {
onClick = { launchEmail() }, modifier = Modifier.padding(vertical = 5.dp)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
modifier = Modifier.fillMaxWidth()) {
Row {
Icon(Icons.Rounded.Mail, stringResource(id = R.string.email))
Text(
stringResource(id = R.string.email_description),
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
modifier = Modifier.padding(start = 10.dp))
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
}
@ -230,13 +198,11 @@ fun SupportScreen(
modifier =
Modifier.clickable {
openWebPage(context.resources.getString(R.string.privacy_policy_url))
}
)
})
Row(
horizontalArrangement = Arrangement.spacedBy(25.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(25.dp)
) {
modifier = Modifier.padding(25.dp)) {
Text("Version: ${BuildConfig.VERSION_NAME}")
Text("Mode: ${if (settings.isKernelEnabled) "Kernel" else "Userspace" }")
}

View File

@ -2,8 +2,9 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@ -13,13 +14,13 @@ import kotlinx.coroutines.launch
@HiltViewModel
class SupportViewModel @Inject constructor(
private val settingsRepo: SettingsDoa
private val settingsRepository: SettingsRepository
) : ViewModel() {
private val _settings = MutableStateFlow(Settings())
val settings get() = _settings.asStateFlow()
val settings = _settings.asStateFlow()
init {
viewModelScope.launch(Dispatchers.IO) {
_settings.value = settingsRepo.getAll().first()
_settings.value = settingsRepository.getAll().first()
}
}
}