feat: ui and splash screen improvements
bump deps allow tunnel stat to stay expanded closes #265
This commit is contained in:
parent
1fb953e2fe
commit
ffad6b331f
|
@ -66,12 +66,12 @@
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.AppSplashScreen"
|
android:theme="@style/Theme.App.Start"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="tiramisu">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.SplashActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.AppSplashScreen">
|
android:theme="@style/Theme.WireguardAutoTunnel">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
@ -83,11 +83,6 @@
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".ui.MainActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/Theme.WireguardAutoTunnel">
|
|
||||||
</activity>
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
|
|
|
@ -25,6 +25,7 @@ class DataStoreManager(
|
||||||
val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
val BATTERY_OPTIMIZE_DISABLE_SHOWN = booleanPreferencesKey("BATTERY_OPTIMIZE_DISABLE_SHOWN")
|
||||||
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
|
val CURRENT_SSID = stringPreferencesKey("CURRENT_SSID")
|
||||||
val IS_PIN_LOCK_ENABLED = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
val IS_PIN_LOCK_ENABLED = booleanPreferencesKey("PIN_LOCK_ENABLED")
|
||||||
|
val IS_TUNNEL_STATS_EXPANDED = booleanPreferencesKey("TUNNEL_STATS_EXPANDED")
|
||||||
}
|
}
|
||||||
|
|
||||||
// preferences
|
// preferences
|
||||||
|
|
|
@ -4,10 +4,12 @@ data class GeneralState(
|
||||||
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
val isLocationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
|
||||||
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
val isBatteryOptimizationDisableShown: Boolean = BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT,
|
||||||
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
val isPinLockEnabled: Boolean = PIN_LOCK_ENABLED_DEFAULT,
|
||||||
|
val isTunnelStatsExpanded: Boolean = IS_TUNNEL_STATS_EXPANDED,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
const val LOCATION_DISCLOSURE_SHOWN_DEFAULT = false
|
||||||
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
const val BATTERY_OPTIMIZATION_DISABLE_SHOWN_DEFAULT = false
|
||||||
const val PIN_LOCK_ENABLED_DEFAULT = false
|
const val PIN_LOCK_ENABLED_DEFAULT = false
|
||||||
|
const val IS_TUNNEL_STATS_EXPANDED = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,5 +20,9 @@ interface AppStateRepository {
|
||||||
|
|
||||||
suspend fun setCurrentSsid(ssid: String)
|
suspend fun setCurrentSsid(ssid: String)
|
||||||
|
|
||||||
|
suspend fun isTunnelStatsExpanded(): Boolean
|
||||||
|
|
||||||
|
suspend fun setTunnelStatsExpanded(expanded: Boolean)
|
||||||
|
|
||||||
val generalStateFlow: Flow<GeneralState>
|
val generalStateFlow: Flow<GeneralState>
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,15 @@ class DataStoreAppStateRepository(
|
||||||
dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid)
|
dataStoreManager.saveToDataStore(DataStoreManager.CURRENT_SSID, ssid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun isTunnelStatsExpanded(): Boolean {
|
||||||
|
return dataStoreManager.getFromStore(DataStoreManager.IS_TUNNEL_STATS_EXPANDED)
|
||||||
|
?: GeneralState.IS_TUNNEL_STATS_EXPANDED
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setTunnelStatsExpanded(expanded: Boolean) {
|
||||||
|
dataStoreManager.saveToDataStore(DataStoreManager.IS_TUNNEL_STATS_EXPANDED, expanded)
|
||||||
|
}
|
||||||
|
|
||||||
override val generalStateFlow: Flow<GeneralState> =
|
override val generalStateFlow: Flow<GeneralState> =
|
||||||
dataStoreManager.preferencesFlow.map { prefs ->
|
dataStoreManager.preferencesFlow.map { prefs ->
|
||||||
prefs?.let { pref ->
|
prefs?.let { pref ->
|
||||||
|
@ -59,6 +68,7 @@ class DataStoreAppStateRepository(
|
||||||
isPinLockEnabled =
|
isPinLockEnabled =
|
||||||
pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
|
pref[DataStoreManager.IS_PIN_LOCK_ENABLED]
|
||||||
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
?: GeneralState.PIN_LOCK_ENABLED_DEFAULT,
|
||||||
|
isTunnelStatsExpanded = pref[DataStoreManager.IS_TUNNEL_STATS_EXPANDED] ?: GeneralState.IS_TUNNEL_STATS_EXPANDED,
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.module
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.NavigationService
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ActivityRetainedComponent
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityRetainedComponent::class)
|
|
||||||
object NavigationModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@ActivityRetainedScoped
|
|
||||||
fun provideNestedNavController(@ApplicationContext context: Context): NavHostController {
|
|
||||||
return NavigationService(context).navController
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.SplashActivity
|
import com.zaneschepke.wireguardautotunnel.ui.MainActivity
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ constructor(
|
||||||
}
|
}
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
val pendingIntent: PendingIntent =
|
val pendingIntent: PendingIntent =
|
||||||
Intent(context, SplashActivity::class.java).let { notificationIntent ->
|
Intent(context, MainActivity::class.java).let { notificationIntent ->
|
||||||
PendingIntent.getActivity(
|
PendingIntent.getActivity(
|
||||||
context,
|
context,
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -2,30 +2,36 @@ package com.zaneschepke.wireguardautotunnel.ui
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavHostController
|
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.takeWhile
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
import kotlinx.coroutines.plus
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AppViewModel
|
class AppViewModel
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
tunnelService: TunnelService,
|
private val tunnelService: Provider<TunnelService>,
|
||||||
val navHostController: NavHostController,
|
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
@ -35,7 +41,7 @@ constructor(
|
||||||
combine(
|
combine(
|
||||||
appDataRepository.settings.getSettingsFlow(),
|
appDataRepository.settings.getSettingsFlow(),
|
||||||
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
appDataRepository.tunnels.getTunnelConfigsFlow(),
|
||||||
tunnelService.vpnState,
|
tunnelService.get().vpnState,
|
||||||
appDataRepository.appState.generalStateFlow,
|
appDataRepository.appState.generalStateFlow,
|
||||||
) { settings, tunnels, tunnelState, generalState ->
|
) { settings, tunnels, tunnelState, generalState ->
|
||||||
AppUiState(
|
AppUiState(
|
||||||
|
@ -44,13 +50,51 @@ constructor(
|
||||||
tunnelState,
|
tunnelState,
|
||||||
generalState,
|
generalState,
|
||||||
)
|
)
|
||||||
}
|
}.stateIn(
|
||||||
.stateIn(
|
|
||||||
viewModelScope + ioDispatcher,
|
viewModelScope + ioDispatcher,
|
||||||
SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
|
SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
|
||||||
_appUiState.value,
|
_appUiState.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val _isAppReady = MutableStateFlow<Boolean>(false)
|
||||||
|
val isAppReady = _isAppReady.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
initPin()
|
||||||
|
initAutoTunnel()
|
||||||
|
initTunnel()
|
||||||
|
appReadyCheck()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun appReadyCheck() {
|
||||||
|
val tunnelCount = appDataRepository.tunnels.count()
|
||||||
|
uiState.takeWhile { it.tunnels.size != tunnelCount }.onCompletion {
|
||||||
|
_isAppReady.emit(true)
|
||||||
|
}.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun initTunnel() {
|
||||||
|
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
||||||
|
val activeTunnels = appDataRepository.tunnels.getActive()
|
||||||
|
if (activeTunnels.isNotEmpty() &&
|
||||||
|
tunnelService.get().getState() == TunnelState.DOWN
|
||||||
|
) {
|
||||||
|
tunnelService.get().startTunnel(activeTunnels.first())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun initPin() {
|
||||||
|
val isPinEnabled = appDataRepository.appState.isPinLockEnabled()
|
||||||
|
if(isPinEnabled) PinManager.initialize(WireGuardAutoTunnel.instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun initAutoTunnel() {
|
||||||
|
val settings = appDataRepository.settings.getSettings()
|
||||||
|
if (settings.isAutoTunnelEnabled) ServiceManager.startWatcherService(WireGuardAutoTunnel.instance)
|
||||||
|
}
|
||||||
|
|
||||||
fun setTunnels(tunnels: TunnelConfigs) = viewModelScope.launch(ioDispatcher) {
|
fun setTunnels(tunnels: TunnelConfigs) = viewModelScope.launch(ioDispatcher) {
|
||||||
_appUiState.emit(
|
_appUiState.emit(
|
||||||
_appUiState.value.copy(
|
_appUiState.value.copy(
|
||||||
|
|
|
@ -4,11 +4,13 @@ import android.os.Bundle
|
||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -19,8 +21,8 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarData
|
import androidx.compose.material3.SnackbarData
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
@ -31,11 +33,12 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.toRoute
|
import androidx.navigation.toRoute
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
||||||
|
@ -44,6 +47,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.isCurrentRoute
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.CustomSnackBar
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarControllerProvider
|
||||||
|
@ -58,6 +62,7 @@ import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import xyz.teamgravity.pin_lock_compose.PinManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
@ -68,11 +73,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var tunnelService: TunnelService
|
lateinit var tunnelService: TunnelService
|
||||||
|
|
||||||
|
private val viewModel by viewModels<AppViewModel>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val isPinLockEnabled = intent.extras?.getBoolean(SplashActivity.IS_PIN_LOCK_ENABLED_KEY)
|
|
||||||
|
|
||||||
enableEdgeToEdge(
|
enableEdgeToEdge(
|
||||||
navigationBarStyle = SystemBarStyle.auto(
|
navigationBarStyle = SystemBarStyle.auto(
|
||||||
lightScrim = Color.Transparent.toArgb(),
|
lightScrim = Color.Transparent.toArgb(),
|
||||||
|
@ -80,10 +84,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
installSplashScreen().apply {
|
||||||
|
setKeepOnScreenCondition {
|
||||||
|
!viewModel.isAppReady.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val appViewModel = hiltViewModel<AppViewModel>()
|
val appUiState by viewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle)
|
||||||
val appUiState by appViewModel.uiState.collectAsStateWithLifecycle(lifecycle = this.lifecycle)
|
val navController = rememberNavController()
|
||||||
val navController = appViewModel.navHostController
|
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
|
||||||
LaunchedEffect(appUiState.vpnState.status) {
|
LaunchedEffect(appUiState.vpnState.status) {
|
||||||
|
@ -95,6 +104,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
context.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
SnackbarControllerProvider { host ->
|
SnackbarControllerProvider { host ->
|
||||||
WireguardAutoTunnelTheme {
|
WireguardAutoTunnelTheme {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
@ -111,7 +121,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.focusable()
|
.focusable()
|
||||||
|
@ -145,32 +154,29 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Surface(modifier = Modifier.fillMaxSize().padding(padding)) {
|
Box(modifier = Modifier.fillMaxSize().padding(padding)) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController,
|
navController,
|
||||||
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
enterTransition = { fadeIn(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||||
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
exitTransition = { fadeOut(tween(Constants.TRANSITION_ANIMATION_TIME)) },
|
||||||
startDestination = (if (isPinLockEnabled == true) Route.Lock else Route.Main),
|
startDestination = (if (appUiState.generalState.isPinLockEnabled == true) Route.Lock else Route.Main),
|
||||||
) {
|
) {
|
||||||
composable<Route.Main> {
|
composable<Route.Main> {
|
||||||
MainScreen(
|
MainScreen(
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
uiState = appUiState,
|
uiState = appUiState,
|
||||||
navController = navController,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Settings> {
|
composable<Route.Settings> {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
appViewModel = appViewModel,
|
appViewModel = viewModel,
|
||||||
uiState = appUiState,
|
uiState = appUiState,
|
||||||
navController = navController,
|
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable<Route.Support> {
|
composable<Route.Support> {
|
||||||
SupportScreen(
|
SupportScreen(
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
navController = navController,
|
|
||||||
appUiState = appUiState,
|
appUiState = appUiState,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +193,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
composable<Route.Option> {
|
composable<Route.Option> {
|
||||||
val args = it.toRoute<Route.Option>()
|
val args = it.toRoute<Route.Option>()
|
||||||
OptionsScreen(
|
OptionsScreen(
|
||||||
navController = navController,
|
|
||||||
tunnelId = args.id,
|
tunnelId = args.id,
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
appUiState = appUiState,
|
appUiState = appUiState,
|
||||||
|
@ -195,8 +200,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
composable<Route.Lock> {
|
composable<Route.Lock> {
|
||||||
PinLockScreen(
|
PinLockScreen(
|
||||||
navController = navController,
|
appViewModel = viewModel,
|
||||||
appViewModel = appViewModel,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,6 +210,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppStateRepository
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
|
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import xyz.teamgravity.pin_lock_compose.PinManager
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Provider
|
|
||||||
|
|
||||||
@SuppressLint("CustomSplashScreen")
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class SplashActivity : ComponentActivity() {
|
|
||||||
@Inject
|
|
||||||
lateinit var appStateRepository: AppStateRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var appDataRepository: AppDataRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var tunnelService: Provider<TunnelService>
|
|
||||||
|
|
||||||
private val appViewModel: AppViewModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
||||||
val splashScreen = installSplashScreen()
|
|
||||||
splashScreen.setKeepOnScreenCondition { true }
|
|
||||||
}
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
|
||||||
val pinLockEnabled = async {
|
|
||||||
appStateRepository.isPinLockEnabled().also {
|
|
||||||
if (it) PinManager.initialize(WireGuardAutoTunnel.instance)
|
|
||||||
}
|
|
||||||
}.await()
|
|
||||||
async {
|
|
||||||
val settings = appDataRepository.settings.getSettings()
|
|
||||||
if (settings.isAutoTunnelEnabled) ServiceManager.startWatcherService(application.applicationContext)
|
|
||||||
if (tunnelService.get().getState() == TunnelState.UP) tunnelService.get().startStatsJob()
|
|
||||||
val activeTunnels = appDataRepository.tunnels.getActive()
|
|
||||||
if (activeTunnels.isNotEmpty() &&
|
|
||||||
tunnelService.get().getState() == TunnelState.DOWN
|
|
||||||
) {
|
|
||||||
tunnelService.get().startTunnel(activeTunnels.first())
|
|
||||||
}
|
|
||||||
}.await()
|
|
||||||
|
|
||||||
async {
|
|
||||||
val tunnels = appDataRepository.tunnels.getAll()
|
|
||||||
appViewModel.setTunnels(tunnels)
|
|
||||||
}.await()
|
|
||||||
|
|
||||||
requestAutoTunnelTileServiceUpdate()
|
|
||||||
|
|
||||||
val intent =
|
|
||||||
Intent(this@SplashActivity, MainActivity::class.java).apply {
|
|
||||||
putExtra(IS_PIN_LOCK_ENABLED_KEY, pinLockEnabled)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val IS_PIN_LOCK_ENABLED_KEY = "is_pin_lock_enabled"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
|
||||||
|
class NestedScrollListener( val onUp: () -> Unit, val onDown: () -> Unit) : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
if (available.y < -1) onDown()
|
||||||
|
if (available.y > 1) onUp()
|
||||||
|
return Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
@ -17,9 +18,10 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
|
||||||
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
|
import com.zaneschepke.wireguardautotunnel.util.extensions.toThreeDecimalPlaceString
|
||||||
|
@ -52,16 +54,17 @@ fun RowListItem(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 15.dp, vertical = 5.dp),
|
.padding(horizontal = 15.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(15.dp),
|
||||||
modifier = Modifier.fillMaxWidth(13 / 20f),
|
modifier = Modifier.fillMaxWidth(13 / 20f),
|
||||||
) {
|
) {
|
||||||
icon()
|
icon()
|
||||||
Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
Text(text, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.labelLarge)
|
||||||
}
|
}
|
||||||
rowButton()
|
rowButton()
|
||||||
}
|
}
|
||||||
|
@ -71,26 +74,32 @@ fun RowListItem(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(end = 10.dp, bottom = 10.dp, start = 10.dp),
|
.padding(end = 10.dp, bottom = 10.dp, start = 45.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
horizontalArrangement = Arrangement.spacedBy(30.dp, Alignment.Start),
|
||||||
) {
|
) {
|
||||||
// TODO change these to string resources
|
|
||||||
val handshakeEpoch = statistics.peerStats(it)!!.latestHandshakeEpochMillis
|
val handshakeEpoch = statistics.peerStats(it)!!.latestHandshakeEpochMillis
|
||||||
val peerTx = statistics.peerStats(it)!!.txBytes
|
|
||||||
val peerRx = statistics.peerStats(it)!!.rxBytes
|
|
||||||
val peerId = it.toBase64().subSequence(0, 3).toString() + "***"
|
val peerId = it.toBase64().subSequence(0, 3).toString() + "***"
|
||||||
val handshakeSec =
|
val handshakeSec =
|
||||||
NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch)
|
NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch)
|
||||||
val handshake =
|
val handshake =
|
||||||
if (handshakeSec == null) "never" else "$handshakeSec secs ago"
|
if (handshakeSec == null) stringResource(R.string.never) else "$handshakeSec " + stringResource(R.string.sec)
|
||||||
|
val peerTx = statistics.peerStats(it)!!.txBytes
|
||||||
|
val peerRx = statistics.peerStats(it)!!.rxBytes
|
||||||
val peerTxMB = NumberUtils.bytesToMB(peerTx).toThreeDecimalPlaceString()
|
val peerTxMB = NumberUtils.bytesToMB(peerTx).toThreeDecimalPlaceString()
|
||||||
val peerRxMB = NumberUtils.bytesToMB(peerRx).toThreeDecimalPlaceString()
|
val peerRxMB = NumberUtils.bytesToMB(peerRx).toThreeDecimalPlaceString()
|
||||||
val fontSize = 9.sp
|
Column(
|
||||||
Text("peer: $peerId", fontSize = fontSize)
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
Text("handshake: $handshake", fontSize = fontSize)
|
) {
|
||||||
Text("tx: $peerTxMB MB", fontSize = fontSize)
|
Text(stringResource(R.string.peer).lowercase() + ": $peerId", style = MaterialTheme.typography.bodySmall)
|
||||||
Text("rx: $peerRxMB MB", fontSize = fontSize)
|
Text("tx: $peerTxMB MB", style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.handshake) + ": $handshake", style = MaterialTheme.typography.bodySmall)
|
||||||
|
Text("rx: $peerRxMB MB", style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
@ -32,6 +33,7 @@ fun ConfigurationToggle(
|
||||||
Text(
|
Text(
|
||||||
label,
|
label,
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Start,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(
|
.weight(
|
||||||
|
|
|
@ -10,10 +10,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
|
||||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
|
|
||||||
|
@ -22,11 +19,9 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavIte
|
||||||
var showBottomBar by rememberSaveable { mutableStateOf(true) }
|
var showBottomBar by rememberSaveable { mutableStateOf(true) }
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
|
|
||||||
showBottomBar = bottomNavItems.firstOrNull {
|
showBottomBar = bottomNavItems.any {
|
||||||
navBackStackEntry?.destination?.hierarchy?.any { dest ->
|
navBackStackEntry?.isCurrentRoute(it.route) == true
|
||||||
bottomNavItems.map { dest.hasRoute(route = it.route::class) }.contains(true)
|
}
|
||||||
} == true
|
|
||||||
} != null
|
|
||||||
|
|
||||||
if (showBottomBar) {
|
if (showBottomBar) {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
|
@ -53,7 +48,7 @@ fun BottomNavBar(navController: NavController, bottomNavItems: List<BottomNavIte
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = item.name,
|
text = item.name,
|
||||||
fontWeight = FontWeight.SemiBold,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
fun NavBackStackEntry?.isCurrentRoute(route: Route): Boolean {
|
fun NavBackStackEntry?.isCurrentRoute(route: Route): Boolean {
|
||||||
return this?.destination?.hierarchy?.any {
|
return this?.destination?.hierarchy?.any {
|
||||||
it.hasRoute(route = route::class)
|
it.hasRoute(route = route::class)
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
||||||
|
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
|
||||||
|
val LocalNavController = compositionLocalOf<NavHostController> {
|
||||||
|
error("NavController was not provided")
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.navigation
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.navigation.NavHostController
|
|
||||||
import androidx.navigation.compose.ComposeNavigator
|
|
||||||
import androidx.navigation.compose.DialogNavigator
|
|
||||||
|
|
||||||
class NavigationService constructor(
|
|
||||||
context: Context,
|
|
||||||
) {
|
|
||||||
val navController = NavHostController(context).apply {
|
|
||||||
navigatorProvider.addNavigator(ComposeNavigator())
|
|
||||||
navigatorProvider.addNavigator(DialogNavigator())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,20 @@
|
||||||
package com.zaneschepke.wireguardautotunnel.ui.common.text
|
package com.zaneschepke.wireguardautotunnel.ui.common.text
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SectionTitle(title: String, padding: Dp) {
|
fun SectionTitle(title: String, padding: Dp) {
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
textAlign = TextAlign.Start,
|
textAlign = TextAlign.Start,
|
||||||
style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.ExtraBold),
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(padding, bottom = 5.dp, top = 5.dp),
|
modifier = Modifier.padding(padding, bottom = 5.dp, top = 5.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,10 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationTextBox
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.ConfigurationToggle
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
|
@ -78,12 +80,21 @@ fun ConfigScreen(tunnelId: Int, focusRequester: FocusRequester) {
|
||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
val clipboardManager: ClipboardManager = LocalClipboardManager.current
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
var showApplicationsDialog by remember { mutableStateOf(false) }
|
var showApplicationsDialog by remember { mutableStateOf(false) }
|
||||||
var showAuthPrompt by remember { mutableStateOf(false) }
|
var showAuthPrompt by remember { mutableStateOf(false) }
|
||||||
var isAuthenticated by remember { mutableStateOf(false) }
|
var isAuthenticated by remember { mutableStateOf(false) }
|
||||||
var configType by remember { mutableStateOf(ConfigType.WIREGUARD) }
|
var configType by remember { mutableStateOf(ConfigType.WIREGUARD) }
|
||||||
|
|
||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
val saved by viewModel.saved.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
|
LaunchedEffect(saved) {
|
||||||
|
if (saved == true) {
|
||||||
|
navController.navigate(Route.Main)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (!uiState.loading && context.isRunningOnTv()) {
|
if (!uiState.loading && context.isRunningOnTv()) {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.content.pm.PackageManager
|
||||||
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 androidx.navigation.NavHostController
|
|
||||||
import com.wireguard.config.Config
|
import com.wireguard.config.Config
|
||||||
import com.wireguard.config.Interface
|
import com.wireguard.config.Interface
|
||||||
import com.wireguard.config.Peer
|
import com.wireguard.config.Peer
|
||||||
|
@ -17,7 +16,6 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||||
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
|
||||||
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
@ -30,8 +28,10 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
@ -44,12 +44,14 @@ class ConfigViewModel
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
constructor(
|
constructor(
|
||||||
private val appDataRepository: AppDataRepository,
|
private val appDataRepository: AppDataRepository,
|
||||||
private val navController: NavHostController,
|
|
||||||
@Assisted val id: Int,
|
@Assisted val id: Int,
|
||||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val packageManager = WireGuardAutoTunnel.instance.packageManager
|
private val packageManager = WireGuardAutoTunnel.instance.packageManager
|
||||||
|
|
||||||
|
private val _saved = MutableSharedFlow<Boolean>()
|
||||||
|
val saved = _saved.asSharedFlow()
|
||||||
|
|
||||||
private val _uiState = MutableStateFlow(ConfigUiState())
|
private val _uiState = MutableStateFlow(ConfigUiState())
|
||||||
val uiState = _uiState.onStart {
|
val uiState = _uiState.onStart {
|
||||||
appDataRepository.tunnels.getById(id)?.let {
|
appDataRepository.tunnels.getById(id)?.let {
|
||||||
|
@ -335,7 +337,7 @@ constructor(
|
||||||
SnackbarController.showMessage(
|
SnackbarController.showMessage(
|
||||||
StringValue.StringResource(R.string.config_changes_saved),
|
StringValue.StringResource(R.string.config_changes_saved),
|
||||||
)
|
)
|
||||||
navController.navigate(Route.Main)
|
_saved.emit(true)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
Timber.e(it)
|
Timber.e(it)
|
||||||
val message = it.message?.substringAfter(":", missingDelimiterValue = "")
|
val message = it.message?.substringAfter(":", missingDelimiterValue = "")
|
||||||
|
|
|
@ -11,7 +11,6 @@ import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
@ -46,20 +45,15 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavController
|
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import com.wireguard.android.backend.GoBackend
|
import com.wireguard.android.backend.GoBackend
|
||||||
|
@ -69,16 +63,19 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.NestedScrollListener
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
|
import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
import com.zaneschepke.wireguardautotunnel.ui.common.dialog.InfoDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
|
import com.zaneschepke.wireguardautotunnel.ui.common.functions.rememberFileImportLauncherForResult
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.GettingStartedLabel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.TunnelImportSheet
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.VpnDeniedDialog
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.corn
|
import com.zaneschepke.wireguardautotunnel.ui.theme.SilverTree
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.mint
|
import com.zaneschepke.wireguardautotunnel.ui.theme.Corn
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.theme.iconSize
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.handshakeStatus
|
import com.zaneschepke.wireguardautotunnel.util.extensions.handshakeStatus
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
|
@ -86,49 +83,31 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.mapPeerStats
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
import com.zaneschepke.wireguardautotunnel.util.extensions.startTunnelBackground
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, focusRequester: FocusRequester, navController: NavController) {
|
fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState, focusRequester: FocusRequester) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
|
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
var showVpnPermissionDialog by remember { mutableStateOf(false) }
|
||||||
val isVisible = rememberSaveable { mutableStateOf(true) }
|
var isFabVisible by rememberSaveable { mutableStateOf(true) }
|
||||||
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
|
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
|
||||||
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
|
||||||
|
|
||||||
val nestedScrollConnection =
|
val nestedScrollConnection = remember {
|
||||||
remember {
|
NestedScrollListener({ isFabVisible = false },{ isFabVisible = true })
|
||||||
object : NestedScrollConnection {
|
|
||||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
|
||||||
// Hide FAB
|
|
||||||
if (available.y < -1) {
|
|
||||||
isVisible.value = false
|
|
||||||
}
|
|
||||||
// Show FAB
|
|
||||||
if (available.y > 1) {
|
|
||||||
isVisible.value = true
|
|
||||||
}
|
|
||||||
return Offset.Zero
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val vpnActivityResultState =
|
val vpnActivityResultState =
|
||||||
rememberLauncherForActivityResult(
|
rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult(),
|
ActivityResultContracts.StartActivityForResult(),
|
||||||
onResult = {
|
onResult = {
|
||||||
val accepted = (it.resultCode == RESULT_OK)
|
if(it.resultCode != RESULT_OK) showVpnPermissionDialog = true
|
||||||
if (accepted) {
|
|
||||||
Timber.d("VPN permission granted")
|
|
||||||
} else {
|
|
||||||
showVpnPermissionDialog = true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -179,17 +158,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
fun onTunnelToggle(checked: Boolean, tunnel: TunnelConfig) {
|
||||||
if (checked) {
|
if (!checked) viewModel.onTunnelStop(tunnel).also { return }
|
||||||
if (uiState.settings.isKernelEnabled) {
|
if (uiState.settings.isKernelEnabled) {
|
||||||
context.startTunnelBackground(tunnel.id)
|
context.startTunnelBackground(tunnel.id)
|
||||||
} else {
|
} else {
|
||||||
viewModel.onTunnelStart(tunnel)
|
viewModel.onTunnelStart(tunnel)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
viewModel.onTunnelStop(
|
|
||||||
tunnel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchQrScanner() {
|
fun launchQrScanner() {
|
||||||
|
@ -223,7 +197,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
contentDescription = icon.name,
|
contentDescription = icon.name,
|
||||||
tint = MaterialTheme.colorScheme.onPrimary,
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
)
|
)
|
||||||
}, focusRequester, isVisible = isVisible.value, onClick = {
|
}, focusRequester, isVisible = isFabVisible, onClick = {
|
||||||
showBottomSheet = true
|
showBottomSheet = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -282,13 +256,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
icon.name,
|
icon.name,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(end = 8.5.dp)
|
.size(iconSize),
|
||||||
.size(25.dp),
|
|
||||||
tint =
|
tint =
|
||||||
if (uiState.settings.isAutoTunnelPaused) {
|
if (uiState.settings.isAutoTunnelPaused) {
|
||||||
Color.Gray
|
Color.Gray
|
||||||
} else {
|
} else {
|
||||||
mint
|
SilverTree
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -330,6 +303,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
it.id == tunnel.id &&
|
it.id == tunnel.id &&
|
||||||
it.isActive
|
it.isActive
|
||||||
}
|
}
|
||||||
|
val expanded = uiState.generalState.isTunnelStatsExpanded
|
||||||
val leadingIconColor =
|
val leadingIconColor =
|
||||||
(
|
(
|
||||||
if (
|
if (
|
||||||
|
@ -339,8 +313,8 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
.map { it.value?.handshakeStatus() }
|
.map { it.value?.handshakeStatus() }
|
||||||
.let { statuses ->
|
.let { statuses ->
|
||||||
when {
|
when {
|
||||||
statuses.all { it == HandshakeStatus.HEALTHY } -> mint
|
statuses.all { it == HandshakeStatus.HEALTHY } -> SilverTree
|
||||||
statuses.any { it == HandshakeStatus.STALE } -> corn
|
statuses.any { it == HandshakeStatus.STALE } -> Corn
|
||||||
statuses.all { it == HandshakeStatus.NOT_STARTED } ->
|
statuses.all { it == HandshakeStatus.NOT_STARTED } ->
|
||||||
Color.Gray
|
Color.Gray
|
||||||
|
|
||||||
|
@ -354,7 +328,6 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val itemFocusRequester = remember { FocusRequester() }
|
val itemFocusRequester = remember { FocusRequester() }
|
||||||
val expanded = remember { mutableStateOf(false) }
|
|
||||||
RowListItem(
|
RowListItem(
|
||||||
icon = {
|
icon = {
|
||||||
val circleIcon = Icons.Rounded.Circle
|
val circleIcon = Icons.Rounded.Circle
|
||||||
|
@ -370,13 +343,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
icon,
|
icon,
|
||||||
icon.name,
|
icon.name,
|
||||||
tint = leadingIconColor,
|
tint = leadingIconColor,
|
||||||
modifier =
|
modifier = Modifier.size(iconSize),
|
||||||
Modifier
|
|
||||||
.padding(
|
|
||||||
end = if (icon == circleIcon) 12.5.dp else 10.dp,
|
|
||||||
start = if (icon == circleIcon) 2.5.dp else 0.dp,
|
|
||||||
)
|
|
||||||
.size(if (icon == circleIcon) 15.dp else 20.dp),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
text = tunnel.name,
|
text = tunnel.name,
|
||||||
|
@ -389,7 +356,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
if (
|
if (
|
||||||
isActive
|
isActive
|
||||||
) {
|
) {
|
||||||
expanded.value = !expanded.value
|
viewModel.onExpandedChanged(!expanded)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedTunnel = tunnel
|
selectedTunnel = tunnel
|
||||||
|
@ -397,7 +364,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
statistics = uiState.vpnState.statistics,
|
statistics = uiState.vpnState.statistics,
|
||||||
expanded = expanded.value,
|
expanded = expanded && isActive,
|
||||||
focusRequester = focusRequester,
|
focusRequester = focusRequester,
|
||||||
rowButton = {
|
rowButton = {
|
||||||
if (
|
if (
|
||||||
|
@ -437,13 +404,11 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!isActive) expanded.value = false
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TunnelSwitch() = Switch(
|
fun TunnelSwitch() = Switch(
|
||||||
modifier = Modifier.focusRequester(itemFocusRequester),
|
modifier = Modifier.focusRequester(itemFocusRequester),
|
||||||
checked = isActive,
|
checked = isActive,
|
||||||
onCheckedChange = { checked ->
|
onCheckedChange = { checked ->
|
||||||
if (!checked) expanded.value = false
|
|
||||||
val intent = if (uiState.settings.isKernelEnabled) null else GoBackend.VpnService.prepare(context)
|
val intent = if (uiState.settings.isKernelEnabled) null else GoBackend.VpnService.prepare(context)
|
||||||
if (intent != null) return@Switch vpnActivityResultState.launch(intent)
|
if (intent != null) return@Switch vpnActivityResultState.launch(intent)
|
||||||
onTunnelToggle(checked, tunnel)
|
onTunnelToggle(checked, tunnel)
|
||||||
|
@ -474,7 +439,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
uiState.vpnState.status == TunnelState.UP &&
|
uiState.vpnState.status == TunnelState.UP &&
|
||||||
(uiState.vpnState.tunnelConfig?.name == tunnel.name)
|
(uiState.vpnState.tunnelConfig?.name == tunnel.name)
|
||||||
) {
|
) {
|
||||||
expanded.value = !expanded.value
|
viewModel.onExpandedChanged(!expanded)
|
||||||
} else {
|
} else {
|
||||||
snackbar.showMessage(
|
snackbar.showMessage(
|
||||||
context.getString(R.string.turn_on_tunnel),
|
context.getString(R.string.turn_on_tunnel),
|
||||||
|
|
|
@ -63,6 +63,10 @@ constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onExpandedChanged(expanded: Boolean) = viewModelScope.launch {
|
||||||
|
appDataRepository.appState.setTunnelStatsExpanded(expanded)
|
||||||
|
}
|
||||||
|
|
||||||
fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
fun onTunnelStart(tunnelConfig: TunnelConfig) = viewModelScope.launch {
|
||||||
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
Timber.i("Starting tunnel ${tunnelConfig.name}")
|
||||||
tunnelService.startTunnel(tunnelConfig)
|
tunnelService.startTunnel(tunnelConfig)
|
||||||
|
|
|
@ -54,6 +54,7 @@ import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
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.config.SubmitConfigurationTextBox
|
import com.zaneschepke.wireguardautotunnel.ui.common.config.SubmitConfigurationTextBox
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
import com.zaneschepke.wireguardautotunnel.ui.screens.main.components.ScrollDismissFab
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.WildcardSupportingLabel
|
import com.zaneschepke.wireguardautotunnel.ui.screens.settings.components.WildcardSupportingLabel
|
||||||
|
@ -68,13 +69,13 @@ import kotlinx.coroutines.delay
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsScreen(
|
fun OptionsScreen(
|
||||||
optionsViewModel: OptionsViewModel = hiltViewModel(),
|
optionsViewModel: OptionsViewModel = hiltViewModel(),
|
||||||
navController: NavController,
|
|
||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
appUiState: AppUiState,
|
appUiState: AppUiState,
|
||||||
tunnelId: Int,
|
tunnelId: Int,
|
||||||
) {
|
) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val config = appUiState.tunnels.first { it.id == tunnelId }
|
val config = appUiState.tunnels.first { it.id == tunnelId }
|
||||||
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
|
|
@ -9,14 +9,16 @@ import androidx.navigation.NavController
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import xyz.teamgravity.pin_lock_compose.PinLock
|
import xyz.teamgravity.pin_lock_compose.PinLock
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PinLockScreen(navController: NavController, appViewModel: AppViewModel) {
|
fun PinLockScreen(appViewModel: AppViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
PinLock(
|
PinLock(
|
||||||
title = { pinExists ->
|
title = { pinExists ->
|
||||||
|
|
|
@ -72,6 +72,7 @@ import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
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.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
import com.zaneschepke.wireguardautotunnel.ui.common.prompt.AuthorizationPrompt
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
import com.zaneschepke.wireguardautotunnel.ui.common.text.SectionTitle
|
||||||
|
@ -95,12 +96,13 @@ fun SettingsScreen(
|
||||||
viewModel: SettingsViewModel = hiltViewModel(),
|
viewModel: SettingsViewModel = hiltViewModel(),
|
||||||
appViewModel: AppViewModel,
|
appViewModel: AppViewModel,
|
||||||
uiState: AppUiState,
|
uiState: AppUiState,
|
||||||
navController: NavController,
|
|
||||||
focusRequester: FocusRequester,
|
focusRequester: FocusRequester,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val snackbar = SnackbarController.current
|
val snackbar = SnackbarController.current
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val isRunningOnTv = context.isRunningOnTv()
|
val isRunningOnTv = context.isRunningOnTv()
|
||||||
|
@ -545,9 +547,11 @@ fun SettingsScreen(
|
||||||
ConfigurationToggle(
|
ConfigurationToggle(
|
||||||
stringResource(R.string.always_on_vpn_support),
|
stringResource(R.string.always_on_vpn_support),
|
||||||
enabled = !(
|
enabled = !(
|
||||||
(uiState.settings.isTunnelOnWifiEnabled ||
|
(
|
||||||
|
uiState.settings.isTunnelOnWifiEnabled ||
|
||||||
uiState.settings.isTunnelOnEthernetEnabled ||
|
uiState.settings.isTunnelOnEthernetEnabled ||
|
||||||
uiState.settings.isTunnelOnMobileDataEnabled) &&
|
uiState.settings.isTunnelOnMobileDataEnabled
|
||||||
|
) &&
|
||||||
uiState.settings.isAutoTunnelEnabled
|
uiState.settings.isAutoTunnelEnabled
|
||||||
),
|
),
|
||||||
checked = uiState.settings.isAlwaysOnVpnEnabled,
|
checked = uiState.settings.isAlwaysOnVpnEnabled,
|
||||||
|
|
|
@ -47,13 +47,15 @@ import com.zaneschepke.wireguardautotunnel.BuildConfig
|
||||||
import com.zaneschepke.wireguardautotunnel.R
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
import com.zaneschepke.wireguardautotunnel.ui.AppUiState
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.Route
|
import com.zaneschepke.wireguardautotunnel.ui.Route
|
||||||
|
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.LocalNavController
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail
|
import com.zaneschepke.wireguardautotunnel.util.extensions.launchSupportEmail
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SupportScreen(navController: NavController, focusRequester: FocusRequester, appUiState: AppUiState) {
|
fun SupportScreen(focusRequester: FocusRequester, appUiState: AppUiState) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
val fillMaxWidth = .85f
|
val fillMaxWidth = .85f
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
|
@ -2,17 +2,37 @@ package com.zaneschepke.wireguardautotunnel.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
val OffWhite = Color(0xFFE5E1E5)
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
val LightGrey = Color(0xFF8D9D9F)
|
||||||
val Pink80 = Color(0xFF492532)
|
val Aqua = Color(0xFF76BEBD)
|
||||||
val virdigris = Color(0xFF5BC0BE)
|
val SilverTree = Color(0xFF6DB58B)
|
||||||
|
val Plantation = Color(0xFF264A49)
|
||||||
|
val Shark = Color(0xFF21272A)
|
||||||
|
val BalticSea = Color(0xFF1C1B1F)
|
||||||
|
val Brick = Color(0xFFCE4257)
|
||||||
|
val Corn = Color(0xFFFBEC5D)
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
sealed class ThemeColors(
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val background: Color,
|
||||||
val Pink40 = Color(0xFFFFFFFF)
|
val surface: Color,
|
||||||
|
val primary: Color,
|
||||||
|
val secondary: Color,
|
||||||
|
val onSurface: Color,
|
||||||
|
) {
|
||||||
|
//TODO fix light theme colors
|
||||||
|
data object Light : ThemeColors(
|
||||||
|
background = LightGrey,
|
||||||
|
surface = OffWhite,
|
||||||
|
primary = Plantation,
|
||||||
|
secondary = OffWhite,
|
||||||
|
onSurface = BalticSea,
|
||||||
|
)
|
||||||
|
|
||||||
// status colors
|
data object Dark : ThemeColors(
|
||||||
val brickRed = Color(0xFFCE4257)
|
background = BalticSea,
|
||||||
val corn = Color(0xFFFBEC5D)
|
surface = Shark,
|
||||||
val pinkRed = Color(0xFFEF476F)
|
primary = Aqua,
|
||||||
val mint = Color(0xFF52B788)
|
secondary = Plantation,
|
||||||
|
onSurface = OffWhite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -18,30 +18,22 @@ import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
private val DarkColorScheme =
|
private val DarkColorScheme =
|
||||||
darkColorScheme(
|
darkColorScheme(
|
||||||
// primary = Purple80,
|
primary = ThemeColors.Dark.primary,
|
||||||
primary = virdigris,
|
surface = ThemeColors.Dark.surface,
|
||||||
secondary = PurpleGrey40,
|
background = ThemeColors.Dark.background,
|
||||||
// secondary = PurpleGrey80,
|
secondaryContainer = ThemeColors.Dark.secondary,
|
||||||
tertiary = Pink40,
|
onSurface = ThemeColors.Dark.onSurface,
|
||||||
surfaceTint = Pink80,
|
onSecondaryContainer = ThemeColors.Dark.primary,
|
||||||
// tertiary = Pink80
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorScheme =
|
private val LightColorScheme =
|
||||||
lightColorScheme(
|
lightColorScheme(
|
||||||
primary = Purple40,
|
primary = ThemeColors.Light.primary,
|
||||||
secondary = PurpleGrey40,
|
surface = ThemeColors.Light.surface,
|
||||||
tertiary = Pink40,
|
background = ThemeColors.Light.background,
|
||||||
surfaceTint = Pink80,
|
secondaryContainer = ThemeColors.Light.secondary,
|
||||||
/* Other default colors to override
|
onSurface = ThemeColors.Light.onSurface,
|
||||||
background = Color(0xFFFFFBFE),
|
onSecondaryContainer = ThemeColors.Light.primary,
|
||||||
surface = Color(0xFFFFFBFE),
|
|
||||||
onPrimary = Color.White,
|
|
||||||
onSecondary = Color.White,
|
|
||||||
onTertiary = Color.White,
|
|
||||||
onBackground = Color(0xFF1C1B1F),
|
|
||||||
onSurface = Color(0xFF1C1B1F),
|
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -60,7 +52,8 @@ fun WireguardAutoTunnelTheme(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useDarkTheme -> DarkColorScheme
|
useDarkTheme -> DarkColorScheme
|
||||||
else -> LightColorScheme
|
//TODO force dark theme for now until light theme designed
|
||||||
|
else -> DarkColorScheme
|
||||||
}
|
}
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
if (!view.isInEditMode) {
|
if (!view.isInEditMode) {
|
||||||
|
|
|
@ -2,35 +2,58 @@ package com.zaneschepke.wireguardautotunnel.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.zaneschepke.wireguardautotunnel.R
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
// Set of Material typography styles to start with
|
||||||
|
|
||||||
|
val inter = FontFamily(
|
||||||
|
Font(R.font.inter),
|
||||||
|
)
|
||||||
|
|
||||||
val Typography =
|
val Typography =
|
||||||
Typography(
|
Typography(
|
||||||
bodyLarge =
|
bodyLarge =
|
||||||
TextStyle(
|
TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = inter,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.5.sp,
|
letterSpacing = 0.5.sp,
|
||||||
),
|
),
|
||||||
/* Other default text styles to override
|
bodySmall = TextStyle(
|
||||||
titleLarge = TextStyle(
|
fontFamily = inter,
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 22.sp,
|
fontSize = 13.sp,
|
||||||
lineHeight = 28.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 1.sp,
|
||||||
|
color = LightGrey,
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
labelLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = inter,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 11.sp,
|
fontSize = 15.sp,
|
||||||
|
lineHeight = 18.sp,
|
||||||
|
letterSpacing = 0.sp,
|
||||||
|
),
|
||||||
|
labelMedium = TextStyle(
|
||||||
|
fontFamily = inter,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 12.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp,
|
||||||
)
|
),
|
||||||
*/
|
titleMedium = TextStyle(
|
||||||
|
fontFamily = inter,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 17.sp,
|
||||||
|
lineHeight = 21.sp,
|
||||||
|
letterSpacing = 0.sp,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val iconSize = 15.dp
|
||||||
|
|
Binary file not shown.
|
@ -196,4 +196,7 @@
|
||||||
<string name="wildcard_supported">Learn about supported wildcards.</string>
|
<string name="wildcard_supported">Learn about supported wildcards.</string>
|
||||||
<string name="details">details</string>
|
<string name="details">details</string>
|
||||||
<string name="show_amnezia_properties">Show Amnezia properties</string>
|
<string name="show_amnezia_properties">Show Amnezia properties</string>
|
||||||
|
<string name="never">never</string>
|
||||||
|
<string name="sec">sec</string>
|
||||||
|
<string name="handshake">handshake</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
<item name="android:windowBackground">@color/black_background</item>
|
<item name="android:windowBackground">@color/black_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.AppSplashScreen" parent="Theme.SplashScreen">
|
<style name="Theme.App.Start" parent="@style/Theme.SplashScreen">
|
||||||
<!--<item name="windowSplashScreenBackground">@color/white</item>-->
|
<item name="windowSplashScreenBackground">@color/black_background</item>
|
||||||
<!-- icon has to be a circle -->
|
|
||||||
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item>
|
||||||
<item name="windowSplashScreenAnimationDuration">500</item>
|
|
||||||
<item name="postSplashScreenTheme">@style/Theme.WireguardAutoTunnel</item>
|
<item name="postSplashScreenTheme">@style/Theme.WireguardAutoTunnel</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -16,19 +16,19 @@ junit = "4.13.2"
|
||||||
kotlinx-serialization-json = "1.7.3"
|
kotlinx-serialization-json = "1.7.3"
|
||||||
lifecycle-runtime-compose = "2.8.6"
|
lifecycle-runtime-compose = "2.8.6"
|
||||||
material3 = "1.3.0"
|
material3 = "1.3.0"
|
||||||
navigationCompose = "2.8.1"
|
navigationCompose = "2.8.2"
|
||||||
pinLockCompose = "1.0.3"
|
pinLockCompose = "1.0.3"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.1"
|
tunnel = "1.2.1"
|
||||||
androidGradlePlugin = "8.6.1"
|
androidGradlePlugin = "8.7.0"
|
||||||
kotlin = "2.0.20"
|
kotlin = "2.0.21"
|
||||||
ksp = "2.0.20-1.0.25"
|
ksp = "2.0.20-1.0.25"
|
||||||
composeBom = "2024.09.02"
|
composeBom = "2024.09.03"
|
||||||
compose = "1.7.2"
|
compose = "1.7.3"
|
||||||
zxingAndroidEmbedded = "4.3.0"
|
zxingAndroidEmbedded = "4.3.0"
|
||||||
coreSplashscreen = "1.0.1"
|
coreSplashscreen = "1.0.1"
|
||||||
gradlePlugins-grgit = "5.2.2"
|
gradlePlugins-grgit = "5.3.0"
|
||||||
|
|
||||||
#plugins
|
#plugins
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
|
|
Loading…
Reference in New Issue