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.room.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import java.io.IOException import java.io.IOException
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test

View File

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

View File

@ -5,9 +5,9 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.model.Settings
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import java.io.IOException import java.io.IOException
import javax.inject.Inject import javax.inject.Inject
@ -17,7 +17,7 @@ import timber.log.Timber
@HiltAndroidApp @HiltAndroidApp
class WireGuardAutoTunnel : Application() { class WireGuardAutoTunnel : Application() {
@Inject @Inject
lateinit var settingsRepo: SettingsDoa lateinit var settingsRepo: SettingsDao
@Inject @Inject
lateinit var dataStoreManager: DataStoreManager 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.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
@Database( @Database(
entities = [Settings::class, TunnelConfig::class], entities = [Settings::class, TunnelConfig::class],
@ -20,7 +20,7 @@ import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
) )
@TypeConverters(DatabaseListConverters::class) @TypeConverters(DatabaseListConverters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun settingDao(): SettingsDoa abstract fun settingDao(): SettingsDao
abstract fun tunnelConfigDoa(): TunnelConfigDao 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 androidx.room.TypeConverter
import kotlinx.serialization.encodeToString 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.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.model.Settings
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface SettingsDoa { interface SettingsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(t: Settings) 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.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao

View File

@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.repository.datastore package com.zaneschepke.wireguardautotunnel.data.datastore
import android.content.Context import android.content.Context
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey 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.ColumnInfo
import androidx.room.Entity 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.ColumnInfo
import androidx.room.Entity 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 android.content.Context
import androidx.room.Room import androidx.room.Room
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn

View File

@ -1,10 +1,14 @@
package com.zaneschepke.wireguardautotunnel.module package com.zaneschepke.wireguardautotunnel.module
import android.content.Context import android.content.Context
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase import com.zaneschepke.wireguardautotunnel.data.AppDatabase
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager 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.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
@ -17,16 +21,28 @@ import javax.inject.Singleton
class RepositoryModule { class RepositoryModule {
@Singleton @Singleton
@Provides @Provides
fun provideSettingsRepository(appDatabase: AppDatabase): SettingsDoa { fun provideSettingsDoa(appDatabase: AppDatabase): SettingsDao {
return appDatabase.settingDao() return appDatabase.settingDao()
} }
@Singleton @Singleton
@Provides @Provides
fun provideTunnelConfigRepository(appDatabase: AppDatabase): TunnelConfigDao { fun provideTunnelConfigDoa(appDatabase: AppDatabase): TunnelConfigDao {
return appDatabase.tunnelConfigDoa() return appDatabase.tunnelConfigDoa()
} }
@Singleton
@Provides
fun provideTunnelConfigRepository(tunnelConfigDao: TunnelConfigDao): TunnelConfigRepository {
return TunnelConfigRepositoryImpl(tunnelConfigDao)
}
@Singleton
@Provides
fun provideSettingsRepository(settingsDao: SettingsDao): SettingsRepository {
return SettingsRepositoryImpl(settingsDao)
}
@Singleton @Singleton
@Provides @Provides
fun providePreferencesDataStore(@ApplicationContext context: Context): DataStoreManager { 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.backend.WgQuickBackend
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.wireguard.android.util.ToolsInstaller 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.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel
import dagger.Module import dagger.Module
@ -51,7 +51,7 @@ class TunnelModule {
fun provideVpnService( fun provideVpnService(
@Userspace userspaceBackend: Backend, @Userspace userspaceBackend: Backend,
@Kernel kernelBackend: Backend, @Kernel kernelBackend: Backend,
settingsDoa: SettingsDoa settingsDoa: SettingsDao
): VpnService { ): VpnService {
return WireGuardTunnel(userspaceBackend, kernelBackend, settingsDoa) return WireGuardTunnel(userspaceBackend, kernelBackend, settingsDoa)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,9 +5,9 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -22,7 +22,7 @@ import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint
class TunnelControlTile : TileService() { class TunnelControlTile : TileService() {
@Inject @Inject
lateinit var settingsRepo: SettingsDoa lateinit var settingsRepo: SettingsDao
@Inject @Inject
lateinit var configRepo: TunnelConfigDao 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.Statistics
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
interface VpnService : Tunnel { interface VpnService : Tunnel {

View File

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

View File

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

View File

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

View File

@ -10,10 +10,11 @@ import androidx.lifecycle.viewModelScope
import com.wireguard.config.Config import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.Constants import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig 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.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
@ -39,7 +40,7 @@ class MainViewModel
constructor( constructor(
private val application: Application, private val application: Application,
private val tunnelRepo: TunnelConfigDao, private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa, private val settingsRepository: SettingsRepository,
private val vpnService: VpnService private val vpnService: VpnService
) : ViewModel() { ) : ViewModel() {
val tunnels get() = tunnelRepo.getAllFlow() val tunnels get() = tunnelRepo.getAllFlow()
@ -53,10 +54,9 @@ constructor(
init { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
settingsRepo.getAllFlow().filter { it.isNotEmpty() }.collect { settingsRepository.getSettings().collect {
val settings = it.first() validateWatcherServiceState(it)
validateWatcherServiceState(settings) _settings.emit(it)
_settings.emit(settings)
} }
} }
} }
@ -79,13 +79,13 @@ constructor(
viewModelScope.launch { viewModelScope.launch {
if (tunnelRepo.count() == 1L) { if (tunnelRepo.count() == 1L) {
ServiceManager.stopWatcherService(application.applicationContext) ServiceManager.stopWatcherService(application.applicationContext)
val settings = settingsRepo.getAll() val settings = settingsRepository.getAll()
if (settings.isNotEmpty()) { if (settings.isNotEmpty()) {
val setting = settings[0] val setting = settings[0]
setting.defaultTunnel = null setting.defaultTunnel = null
setting.isAutoTunnelEnabled = false setting.isAutoTunnelEnabled = false
setting.isAlwaysOnVpnEnabled = false setting.isAlwaysOnVpnEnabled = false
settingsRepo.save(setting) saveSettings(setting)
} }
} }
tunnelRepo.delete(tunnel) 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( private fun getFileName(
context: Context, context: Context,
uri: Uri uri: Uri
@ -268,7 +273,7 @@ constructor(
defaultTunnel = selectedTunnel.toString() 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.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -38,13 +38,11 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
@ -72,17 +70,15 @@ import com.wireguard.android.backend.Tunnel
import com.wireguard.android.backend.WgQuickBackend import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel 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.ClickableIconButton
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import java.io.File import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn( @OptIn(
ExperimentalPermissionsApi::class, ExperimentalPermissionsApi::class,
ExperimentalLayoutApi::class, ExperimentalLayoutApi::class,
@ -102,33 +98,26 @@ fun SettingsScreen(
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val settings by viewModel.settings.collectAsStateWithLifecycle() val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val tunnels by viewModel.tunnels.collectAsStateWithLifecycle()
val vpnState = viewModel.vpnState.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
var isBackgroundLocationGranted by remember { mutableStateOf(true) } var isBackgroundLocationGranted by remember { mutableStateOf(true) }
var didExportFiles by remember { mutableStateOf(false) } var didExportFiles by remember { mutableStateOf(false) }
var showAuthPrompt by remember { mutableStateOf(false) } var showAuthPrompt by remember { mutableStateOf(false) }
var isLocationDisclosureShown by rememberSaveable {
mutableStateOf(false)
}
val screenPadding = 5.dp val screenPadding = 5.dp
val fillMaxWidth = .85f val fillMaxWidth = .85f
LaunchedEffect(Unit) { //TODO add error collecting and displaying for WGTunnelErrors
isLocationDisclosureShown = viewModel.isLocationDisclosureShown()
}
fun exportAllConfigs() { fun exportAllConfigs() {
try { 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 -> files.forEachIndexed { index, file ->
file.outputStream().use { file.outputStream().use {
it.write(tunnels[index].wgQuick.toByteArray()) it.write(uiState.tunnels[index].wgQuick.toByteArray())
} }
} }
FileUtils.saveFilesToZip(context, files) FileUtils.saveFilesToZip(context, files)
@ -163,20 +152,31 @@ fun SettingsScreen(
fun openSettings() { fun openSettings() {
scope.launch { scope.launch {
val intentSettings = val intentSettings =
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
intentSettings.data = intentSettings.data =
Uri.fromParts("package", context.packageName, null) Uri.fromParts("package", context.packageName, null)
context.startActivity(intentSettings) 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val backgroundLocationState = val backgroundLocationState =
rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION) rememberPermissionState(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
isBackgroundLocationGranted = if (!backgroundLocationState.status.isGranted) { isBackgroundLocationGranted = if (!backgroundLocationState.status.isGranted) {
false false
} else { } else {
if(!isLocationDisclosureShown) { SideEffect {
viewModel.setLocationDisclosureShown() viewModel.setLocationDisclosureShown()
} }
true true
@ -194,7 +194,7 @@ fun SettingsScreen(
} }
} }
AnimatedVisibility(!isLocationDisclosureShown) { AnimatedVisibility(!uiState.isLocationDisclosureShown) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
@ -270,7 +270,7 @@ fun SettingsScreen(
) )
} }
if (tunnels.isEmpty()) { if (uiState.tunnels.isEmpty()) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@ -330,17 +330,15 @@ fun SettingsScreen(
) )
ConfigurationToggle( ConfigurationToggle(
stringResource(id = R.string.tunnel_on_wifi), stringResource(id = R.string.tunnel_on_wifi),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnWifiEnabled, checked = uiState.settings.isTunnelOnWifiEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { viewModel.onToggleTunnelOnWifi()
viewModel.onToggleTunnelOnWifi()
}
}, },
modifier = Modifier.focusRequester(focusRequester) modifier = Modifier.focusRequester(focusRequester)
) )
AnimatedVisibility(visible = settings.isTunnelOnWifiEnabled) { AnimatedVisibility(visible = uiState.settings.isTunnelOnWifiEnabled) {
Column { Column {
FlowRow( FlowRow(
modifier = Modifier modifier = Modifier
@ -348,19 +346,17 @@ fun SettingsScreen(
.fillMaxWidth(), .fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(5.dp) horizontalArrangement = Arrangement.spacedBy(5.dp)
) { ) {
settings.trustedNetworkSSIDs.forEach { ssid -> uiState.settings.trustedNetworkSSIDs.forEach { ssid ->
ClickableIconButton( ClickableIconButton(
onIconClick = { onIconClick = {
scope.launch { viewModel.onDeleteTrustedSSID(ssid)
viewModel.onDeleteTrustedSSID(ssid)
}
}, },
text = ssid, text = ssid,
icon = Icons.Filled.Close, 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( Text(
stringResource(R.string.none), stringResource(R.string.none),
fontStyle = FontStyle.Italic, fontStyle = FontStyle.Italic,
@ -369,7 +365,7 @@ fun SettingsScreen(
} }
} }
OutlinedTextField( OutlinedTextField(
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
value = currentText, value = currentText,
onValueChange = { currentText = it }, onValueChange = { currentText = it },
label = { Text(stringResource(R.string.add_trusted_ssid)) }, label = { Text(stringResource(R.string.add_trusted_ssid)) },
@ -418,35 +414,29 @@ fun SettingsScreen(
} }
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.tunnel_mobile_data), stringResource(R.string.tunnel_mobile_data),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnMobileDataEnabled, checked = uiState.settings.isTunnelOnMobileDataEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { viewModel.onToggleTunnelOnMobileData()
viewModel.onToggleTunnelOnMobileData()
}
} }
) )
ConfigurationToggle( ConfigurationToggle(
stringResource(id = R.string.tunnel_on_ethernet), stringResource(id = R.string.tunnel_on_ethernet),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = settings.isTunnelOnEthernetEnabled, checked = uiState.settings.isTunnelOnEthernetEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { viewModel.onToggleTunnelOnEthernet()
viewModel.onToggleTunnelOnEthernet()
}
} }
) )
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.battery_saver), stringResource(R.string.battery_saver),
enabled = !(settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled), enabled = !(uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled),
checked = settings.isBatterySaverEnabled, checked = uiState.settings.isBatterySaverEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { viewModel.onToggleBatterySaver()
viewModel.onToggleBatterySaver()
}
} }
) )
Row( Row(
@ -458,9 +448,9 @@ fun SettingsScreen(
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
TextButton( TextButton(
enabled = !settings.isAlwaysOnVpnEnabled, enabled = !uiState.settings.isAlwaysOnVpnEnabled,
onClick = { onClick = {
if (!isAllAutoTunnelPermissionsEnabled() && settings.isTunnelOnWifiEnabled) { if (!isAllAutoTunnelPermissionsEnabled() && uiState.settings.isTunnelOnWifiEnabled) {
val message = val message =
if (!isBackgroundLocationGranted) { if (!isBackgroundLocationGranted) {
context.getString(R.string.background_location_required) context.getString(R.string.background_location_required)
@ -471,14 +461,12 @@ fun SettingsScreen(
} }
showSnackbarMessage(message) showSnackbarMessage(message)
} else { } else {
scope.launch { viewModel.toggleAutoTunnel()
viewModel.toggleAutoTunnel()
}
} }
} }
) { ) {
val autoTunnelButtonText = val autoTunnelButtonText =
if (settings.isAutoTunnelEnabled) { if (uiState.settings.isAutoTunnelEnabled) {
stringResource(R.string.disable_auto_tunnel) stringResource(R.string.disable_auto_tunnel)
} else { } else {
stringResource(id = R.string.enable_auto_tunnel) stringResource(id = R.string.enable_auto_tunnel)
@ -510,19 +498,13 @@ fun SettingsScreen(
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.use_kernel), stringResource(R.string.use_kernel),
enabled = !( enabled = !(
settings.isAutoTunnelEnabled || settings.isAlwaysOnVpnEnabled || uiState.settings.isAutoTunnelEnabled || uiState.settings.isAlwaysOnVpnEnabled ||
(vpnState.value == Tunnel.State.UP) (uiState.tunnelState == Tunnel.State.UP)
), ),
checked = settings.isKernelEnabled, checked = uiState.settings.isKernelEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { viewModel.onToggleKernelMode()
try {
viewModel.onToggleKernelMode()
} catch (e: WgTunnelException) {
showSnackbarMessage(e.message)
}
}
} }
) )
} }
@ -550,8 +532,8 @@ fun SettingsScreen(
) )
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.always_on_vpn_support), stringResource(R.string.always_on_vpn_support),
enabled = !settings.isAutoTunnelEnabled, enabled = !uiState.settings.isAutoTunnelEnabled,
checked = settings.isAlwaysOnVpnEnabled, checked = uiState.settings.isAlwaysOnVpnEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { scope.launch {
@ -562,7 +544,7 @@ fun SettingsScreen(
ConfigurationToggle( ConfigurationToggle(
stringResource(R.string.enabled_app_shortcuts), stringResource(R.string.enabled_app_shortcuts),
enabled = true, enabled = true,
checked = settings.isShortcutsEnabled, checked = uiState.settings.isShortcutsEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { onCheckChanged = {
scope.launch { 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 android.os.Build
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.wireguard.android.backend.Tunnel
import com.wireguard.android.util.RootShell import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.repository.datastore.DataStoreManager import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.WgTunnelException import com.zaneschepke.wireguardautotunnel.util.WgTunnelException
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -28,24 +28,27 @@ class SettingsViewModel
constructor( constructor(
private val application: Application, private val application: Application,
private val tunnelRepo: TunnelConfigDao, private val tunnelRepo: TunnelConfigDao,
private val settingsRepo: SettingsDoa, private val settingsRepository: SettingsRepository,
private val dataStoreManager: DataStoreManager, private val dataStoreManager: DataStoreManager,
private val rootShell: RootShell, private val rootShell: RootShell,
private val vpnService: VpnService private val vpnService: VpnService
) : ViewModel() { ) : ViewModel() {
val settings = settingsRepo.getSettingsFlow().stateIn(viewModelScope, val uiState = combine(
SharingStarted.WhileSubscribed(5_000L), Settings()) settingsRepository.getSettings(),
val tunnels = tunnelRepo.getAllFlow().stateIn(viewModelScope, tunnelRepo.getAllFlow(),
SharingStarted.WhileSubscribed(5_000L), emptyList()) vpnService.state,
val vpnState get() = vpnService.state.stateIn(viewModelScope, dataStoreManager.locationDisclosureFlow,
SharingStarted.WhileSubscribed(5_000L), Tunnel.State.DOWN) ){ 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() val trimmed = ssid.trim()
if (!settings.value.trustedNetworkSSIDs.contains(trimmed)) { if (!uiState.value.settings.trustedNetworkSSIDs.contains(trimmed)) {
settings.value.trustedNetworkSSIDs.add(trimmed) uiState.value.settings.trustedNetworkSSIDs.add(trimmed)
settingsRepo.save(settings.value) saveSettings(uiState.value.settings)
} else { } else {
throw WgTunnelException("SSID already exists.") throw WgTunnelException("SSID already exists.")
} }
@ -55,39 +58,37 @@ constructor(
return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) ?: false return dataStoreManager.getFromStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN) ?: false
} }
fun setLocationDisclosureShown() { fun setLocationDisclosureShown() = viewModelScope.launch {
viewModelScope.launch {
dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, true) dataStoreManager.saveToDataStore(DataStoreManager.LOCATION_DISCLOSURE_SHOWN, true)
}
} }
suspend fun onToggleTunnelOnMobileData() { fun onToggleTunnelOnMobileData() {
settingsRepo.save( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isTunnelOnMobileDataEnabled = !settings.value.isTunnelOnMobileDataEnabled isTunnelOnMobileDataEnabled = !uiState.value.settings.isTunnelOnMobileDataEnabled
) )
) )
} }
suspend fun onDeleteTrustedSSID(ssid: String) { fun onDeleteTrustedSSID(ssid: String) {
settings.value.trustedNetworkSSIDs.remove(ssid) uiState.value.settings.trustedNetworkSSIDs.remove(ssid)
settingsRepo.save(settings.value) saveSettings(uiState.value.settings)
} }
private suspend fun getDefaultTunnelOrFirst() : String { 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() val defaultTunnel = getDefaultTunnelOrFirst()
if (settings.value.isAutoTunnelEnabled) { if (uiState.value.settings.isAutoTunnelEnabled) {
ServiceManager.stopWatcherService(application) ServiceManager.stopWatcherService(application)
} else { } else {
ServiceManager.startWatcherService(application, defaultTunnel) ServiceManager.startWatcherService(application, defaultTunnel)
} }
saveSettings( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isAutoTunnelEnabled = settings.value.isAutoTunnelEnabled, isAutoTunnelEnabled = uiState.value.settings.isAutoTunnelEnabled,
defaultTunnel = defaultTunnel defaultTunnel = defaultTunnel
) )
) )
@ -95,20 +96,20 @@ constructor(
suspend fun onToggleAlwaysOnVPN() { suspend fun onToggleAlwaysOnVPN() {
val updatedSettings = val updatedSettings =
settings.value.copy( uiState.value.settings.copy(
isAlwaysOnVpnEnabled = !settings.value.isAlwaysOnVpnEnabled, isAlwaysOnVpnEnabled = !uiState.value.settings.isAlwaysOnVpnEnabled,
defaultTunnel = getDefaultTunnelOrFirst() defaultTunnel = getDefaultTunnelOrFirst()
) )
saveSettings(updatedSettings) saveSettings(updatedSettings)
} }
private suspend fun saveSettings(settings: Settings) { private fun saveSettings(settings: Settings) = viewModelScope.launch {
settingsRepo.save(settings) settingsRepository.save(settings)
} }
suspend fun onToggleTunnelOnEthernet() { fun onToggleTunnelOnEthernet() {
saveSettings(settings.value.copy( saveSettings(uiState.value.settings.copy(
isTunnelOnEthernetEnabled = !settings.value.isTunnelOnEthernetEnabled isTunnelOnEthernetEnabled = !uiState.value.settings.isTunnelOnEthernetEnabled
)) ))
} }
@ -122,40 +123,40 @@ constructor(
return (!isLocationServicesEnabled() && Build.VERSION.SDK_INT > Build.VERSION_CODES.P) return (!isLocationServicesEnabled() && Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
} }
suspend fun onToggleShortcutsEnabled() { fun onToggleShortcutsEnabled() {
saveSettings( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isShortcutsEnabled = !settings.value.isShortcutsEnabled isShortcutsEnabled = !uiState.value.settings.isShortcutsEnabled
) )
) )
} }
suspend fun onToggleBatterySaver() { fun onToggleBatterySaver() {
saveSettings( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isBatterySaverEnabled = !settings.value.isBatterySaverEnabled isBatterySaverEnabled = !uiState.value.settings.isBatterySaverEnabled
) )
) )
} }
private suspend fun saveKernelMode(on: Boolean) { private fun saveKernelMode(on: Boolean) {
saveSettings( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isKernelEnabled = on isKernelEnabled = on
) )
) )
} }
suspend fun onToggleTunnelOnWifi() { fun onToggleTunnelOnWifi() {
saveSettings( saveSettings(
settings.value.copy( uiState.value.settings.copy(
isTunnelOnWifiEnabled = !settings.value.isTunnelOnWifiEnabled isTunnelOnWifiEnabled = !uiState.value.settings.isTunnelOnWifiEnabled
) )
) )
} }
suspend fun onToggleKernelMode() { fun onToggleKernelMode() = viewModelScope.launch {
if (!settings.value.isKernelEnabled) { if (!uiState.value.settings.isKernelEnabled) {
try { try {
rootShell.start() rootShell.start()
Timber.d("Root shell accepted!") Timber.d("Root shell accepted!")

View File

@ -51,194 +51,160 @@ import com.zaneschepke.wireguardautotunnel.BuildConfig
import com.zaneschepke.wireguardautotunnel.Constants import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsViewModel
@Composable @Composable
fun SupportScreen( fun SupportScreen(
viewModel: SettingsViewModel = hiltViewModel(), viewModel: SupportViewModel = hiltViewModel(),
padding: PaddingValues, padding: PaddingValues,
focusRequester: FocusRequester focusRequester: FocusRequester
) { ) {
val context = LocalContext.current val context = LocalContext.current
val fillMaxWidth = .85f val fillMaxWidth = .85f
val settings by viewModel.settings.collectAsStateWithLifecycle() val settings by viewModel.settings.collectAsStateWithLifecycle()
fun openWebPage(url: String) { fun openWebPage(url: String) {
val webpage: Uri = Uri.parse(url) val webpage: Uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, webpage) val intent = Intent(Intent.ACTION_VIEW, webpage)
context.startActivity(intent) context.startActivity(intent)
} }
fun launchEmail() { fun launchEmail() {
val intent = val intent =
Intent(Intent.ACTION_SEND).apply { Intent(Intent.ACTION_SEND).apply {
type = Constants.EMAIL_MIME_TYPE type = Constants.EMAIL_MIME_TYPE
putExtra(Intent.EXTRA_EMAIL, context.getString(R.string.my_email)) putExtra(Intent.EXTRA_EMAIL, context.getString(R.string.my_email))
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject)) putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject))
} }
startActivity( startActivity(context, createChooser(intent, context.getString(R.string.email_chooser)), null)
context, }
createChooser(intent, context.getString(R.string.email_chooser)),
null
)
}
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
modifier = modifier =
Modifier Modifier.fillMaxSize()
.fillMaxSize() .verticalScroll(rememberScrollState())
.verticalScroll(rememberScrollState()) .focusable()
.focusable() .padding(padding)) {
.padding(padding)
) {
Surface( Surface(
tonalElevation = 2.dp, tonalElevation = 2.dp,
shadowElevation = 2.dp, shadowElevation = 2.dp,
shape = RoundedCornerShape(12.dp), shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
modifier = modifier =
( (if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) {
if (WireGuardAutoTunnel.isRunningOnAndroidTv(context)) { Modifier.height(IntrinsicSize.Min)
Modifier .fillMaxWidth(fillMaxWidth)
.height(IntrinsicSize.Min) .padding(top = 10.dp)
.fillMaxWidth(fillMaxWidth)
.padding(top = 10.dp)
} else { } else {
Modifier Modifier.fillMaxWidth(fillMaxWidth).padding(top = 20.dp)
.fillMaxWidth(fillMaxWidth) })
.padding(top = 20.dp) .padding(bottom = 25.dp)) {
} Column(modifier = Modifier.padding(20.dp)) {
).padding(bottom = 25.dp)
) {
Column(modifier = Modifier.padding(20.dp)) {
Text( Text(
stringResource(R.string.thank_you), stringResource(R.string.thank_you),
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
modifier = Modifier.padding(bottom = 20.dp), modifier = Modifier.padding(bottom = 20.dp),
fontSize = 16.sp fontSize = 16.sp)
)
Text( Text(
stringResource(id = R.string.support_help_text), stringResource(id = R.string.support_help_text),
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
fontSize = 16.sp, fontSize = 16.sp,
modifier = Modifier.padding(bottom = 20.dp) modifier = Modifier.padding(bottom = 20.dp))
) TextButton(
TextButton(onClick = { onClick = { openWebPage(context.resources.getString(R.string.docs_url)) },
openWebPage(context.resources.getString(R.string.docs_url)) modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester)) {
}, modifier = Modifier.padding(vertical = 5.dp).focusRequester(focusRequester)) { Row(
Row( horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth() Row {
) { Icon(Icons.Rounded.Book, stringResource(id = R.string.docs))
Row { Text(
Icon(Icons.Rounded.Book, stringResource(id = R.string.docs)) stringResource(id = R.string.docs_description),
Text( textAlign = TextAlign.Justify,
stringResource(id = R.string.docs_description), modifier = Modifier.padding(start = 10.dp))
textAlign = TextAlign.Justify, }
modifier = Modifier.padding(start = 10.dp) Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
) }
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
} }
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp) Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton( TextButton(
onClick = { openWebPage(context.resources.getString(R.string.discord_url)) }, onClick = { openWebPage(context.resources.getString(R.string.discord_url)) },
modifier = Modifier.padding(vertical = 5.dp) modifier = Modifier.padding(vertical = 5.dp)) {
) { Row(
Row( horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth() Row {
) { Icon(
Row { imageVector = ImageVector.vectorResource(R.drawable.discord),
Icon( stringResource(id = R.string.discord),
imageVector = ImageVector.vectorResource(R.drawable.discord), Modifier.size(25.dp))
stringResource( Text(
id = R.string.discord stringResource(id = R.string.discord_description),
), textAlign = TextAlign.Justify,
Modifier.size(25.dp) modifier = Modifier.padding(start = 10.dp))
) }
Text( Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
stringResource(id = R.string.discord_description), }
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
} }
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp) Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton( TextButton(
onClick = { openWebPage(context.resources.getString(R.string.github_url)) }, onClick = { openWebPage(context.resources.getString(R.string.github_url)) },
modifier = Modifier.padding(vertical = 5.dp) modifier = Modifier.padding(vertical = 5.dp)) {
) { Row(
Row( horizontalArrangement = Arrangement.SpaceBetween,
horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically,
verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth() Row {
) { Icon(
Row { imageVector = ImageVector.vectorResource(R.drawable.github),
Icon( stringResource(id = R.string.github),
imageVector = ImageVector.vectorResource(R.drawable.github), Modifier.size(25.dp))
stringResource( Text(
id = R.string.github "Open an issue",
), textAlign = TextAlign.Justify,
Modifier.size(25.dp) modifier = Modifier.padding(start = 10.dp))
) }
Text( Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
"Open an issue", }
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp)
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
} }
}
Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp) Divider(color = MaterialTheme.colorScheme.onBackground, thickness = 0.5.dp)
TextButton( TextButton(
onClick = { launchEmail() }, onClick = { launchEmail() }, modifier = Modifier.padding(vertical = 5.dp)) {
modifier = Modifier.padding(vertical = 5.dp) Row(
) { horizontalArrangement = Arrangement.SpaceBetween,
Row( verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
verticalAlignment = Alignment.CenterVertically, Row {
modifier = Modifier.fillMaxWidth() Icon(Icons.Rounded.Mail, stringResource(id = R.string.email))
) { Text(
Row { stringResource(id = R.string.email_description),
Icon(Icons.Rounded.Mail, stringResource(id = R.string.email)) textAlign = TextAlign.Justify,
Text( modifier = Modifier.padding(start = 10.dp))
stringResource(id = R.string.email_description), }
textAlign = TextAlign.Justify, Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
modifier = Modifier.padding(start = 10.dp) }
)
}
Icon(Icons.Rounded.ArrowForward, stringResource(id = R.string.go))
} }
} }
} }
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Text( Text(
stringResource(id = R.string.privacy_policy), stringResource(id = R.string.privacy_policy),
style = TextStyle(textDecoration = TextDecoration.Underline), style = TextStyle(textDecoration = TextDecoration.Underline),
fontSize = 16.sp, fontSize = 16.sp,
modifier = modifier =
Modifier.clickable { Modifier.clickable {
openWebPage(context.resources.getString(R.string.privacy_policy_url)) openWebPage(context.resources.getString(R.string.privacy_policy_url))
} })
)
Row( Row(
horizontalArrangement = Arrangement.spacedBy(25.dp), horizontalArrangement = Arrangement.spacedBy(25.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(25.dp) modifier = Modifier.padding(25.dp)) {
) { Text("Version: ${BuildConfig.VERSION_NAME}")
Text("Version: ${BuildConfig.VERSION_NAME}") Text("Mode: ${if (settings.isKernelEnabled) "Kernel" else "Userspace" }")
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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.repository.model.Settings import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -13,13 +14,13 @@ import kotlinx.coroutines.launch
@HiltViewModel @HiltViewModel
class SupportViewModel @Inject constructor( class SupportViewModel @Inject constructor(
private val settingsRepo: SettingsDoa private val settingsRepository: SettingsRepository
) : ViewModel() { ) : ViewModel() {
private val _settings = MutableStateFlow(Settings()) private val _settings = MutableStateFlow(Settings())
val settings get() = _settings.asStateFlow() val settings = _settings.asStateFlow()
init { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
_settings.value = settingsRepo.getAll().first() _settings.value = settingsRepository.getAll().first()
} }
} }
} }