From 68b0902398cdc59128d863b2bd3081533090c63a Mon Sep 17 00:00:00 2001 From: Zane Schepke Date: Mon, 17 Jul 2023 16:32:16 -0400 Subject: [PATCH] feat: add tunnel details screen and handshake monitoring Adds details screen which display details of tunnel configuration as well as last handshake and rx/tx of peer. Adds last handshake monitoring with statuses and thresholds. Adds handshake/connection notifications based on last successful handshake. Adds status LED next to tunnel on main screen. Fixes bug where first click on QR code could result in nothing happening if QR code module is being downloaded. Now shows message to user. Fixes bug where changes made after editing tunnel were not propagated to settings if that tunnel was configured as the default tunnel. Fixes bug causing crash if wrong config file selected Update README Closes #7, Closes #6 --- README.md | 2 +- app/build.gradle.kts | 9 +- app/src/main/AndroidManifest.xml | 4 +- .../{ => receiver}/BootReceiver.kt | 10 +- .../receiver/NotificationActionReceiver.kt | 56 ++++++++ .../service/barcode/QRScanner.kt | 1 + .../WireGuardConnectivityWatcherService.kt | 16 ++- .../foreground/WireGuardTunnelService.kt | 59 +++++++- .../service/network/BaseNetworkService.kt | 3 - .../notification/NotificationService.kt | 3 + .../notification/WireGuardNotification.kt | 36 +++-- .../service/tunnel/HandshakeStatus.kt | 14 ++ .../service/tunnel/VpnService.kt | 5 + .../service/tunnel/WireGuardTunnel.kt | 72 +++++++++- .../wireguardautotunnel/ui/MainActivity.kt | 7 +- .../wireguardautotunnel/ui/Routes.kt | 3 +- .../ui/common/RowListItem.kt | 20 ++- .../ui/screens/config/ConfigViewModel.kt | 15 +- .../ui/screens/detail/DetailScreen.kt | 135 ++++++++++++++++++ .../ui/screens/detail/DetailViewModel.kt | 45 ++++++ .../ui/screens/main/MainScreen.kt | 102 ++++++++----- .../ui/screens/main/MainViewModel.kt | 43 +++--- .../wireguardautotunnel/ui/theme/Color.kt | 7 +- .../wireguardautotunnel/util/NumberUtils.kt | 25 ++++ app/src/main/res/values/strings.xml | 19 +++ asset/main_screen.png | Bin 42320 -> 43591 bytes asset/main_screen_old.png | Bin 0 -> 42320 bytes build.gradle.kts | 8 +- 28 files changed, 612 insertions(+), 107 deletions(-) rename app/src/main/java/com/zaneschepke/wireguardautotunnel/{ => receiver}/BootReceiver.kt (84%) create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt create mode 100644 app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt create mode 100644 asset/main_screen_old.png diff --git a/README.md b/README.md index 5581c80..12dc1ee 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This is an alternative Android Application for [WireGuard](https://www.wireguard ## Screenshots

- + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9169f10..eab4a92 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ android { compileSdk = 33 val versionMajor = 2 - val versionMinor = 0 - val versionPatch = 3 + val versionMinor = 1 + val versionPatch = 2 val versionBuild = 0 defaultConfig { @@ -54,7 +54,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.4.7" + kotlinCompilerExtensionVersion = "1.4.8" } packaging { resources { @@ -83,7 +83,7 @@ dependencies { debugImplementation("androidx.compose.ui:ui-test-manifest") //wireguard tunnel - implementation("com.wireguard.android:tunnel:1.0.20230405") + implementation("com.wireguard.android:tunnel:1.0.20230427") //logging implementation("com.jakewharton.timber:timber:5.0.1") @@ -127,6 +127,7 @@ dependencies { } + kapt { correctErrorTypes = true } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e594a4..580267a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + @@ -59,12 +60,13 @@ android:stopWithTask="false" android:exported="false"> - + diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt similarity index 84% rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt index e1d35f8..ca90313 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/BootReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt @@ -1,8 +1,9 @@ -package com.zaneschepke.wireguardautotunnel +package com.zaneschepke.wireguardautotunnel.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.repository.Repository import com.zaneschepke.wireguardautotunnel.service.foreground.Action import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker @@ -11,7 +12,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch @@ -23,21 +23,19 @@ class BootReceiver : BroadcastReceiver() { @Inject lateinit var settingsRepo : Repository - @OptIn(DelicateCoroutinesApi::class) override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED) { CoroutineScope(SupervisorJob()).launch { try { val settings = settingsRepo.getAll() if (!settings.isNullOrEmpty()) { - val setting = settings[0] + val setting = settings.first() if (setting.isAutoTunnelEnabled && setting.defaultTunnel != null) { - val defaultTunnel = TunnelConfig.from(setting.defaultTunnel!!) ServiceTracker.actionOnService( Action.START, context, WireGuardConnectivityWatcherService::class.java, mapOf(context.resources.getString(R.string.tunnel_extras_key) to - defaultTunnel.toString()) + setting.defaultTunnel!!) ) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt new file mode 100644 index 0000000..2f79f54 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt @@ -0,0 +1,56 @@ +package com.zaneschepke.wireguardautotunnel.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.repository.Repository +import com.zaneschepke.wireguardautotunnel.service.foreground.Action +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker +import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationActionReceiver : BroadcastReceiver() { + + @Inject + lateinit var settingsRepo : Repository + override fun onReceive(context: Context, intent: Intent?) { + CoroutineScope(SupervisorJob()).launch { + try { + val settings = settingsRepo.getAll() + if (!settings.isNullOrEmpty()) { + val setting = settings.first() + if (setting.defaultTunnel != null) { + ServiceTracker.actionOnService( + Action.STOP, context, + WireGuardTunnelService::class.java, + mapOf( + context.resources.getString(R.string.tunnel_extras_key) to + setting.defaultTunnel!! + ) + ) + delay(1000) + ServiceTracker.actionOnService( + Action.START, context, + WireGuardTunnelService::class.java, + mapOf( + context.resources.getString(R.string.tunnel_extras_key) to + setting.defaultTunnel!! + ) + ) + } + } + } finally { + cancel() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt index ab71f7c..dd502c7 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/barcode/QRScanner.kt @@ -13,6 +13,7 @@ class QRScanner @Inject constructor(private val gmsBarcodeScanner: GmsBarcodeSca gmsBarcodeScanner.startScan().addOnSuccessListener { trySend(it.rawValue) }.addOnFailureListener { + trySend(it.message) Timber.e(it.message) } awaitClose { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt index 9845fbf..4e5b792 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt @@ -20,7 +20,6 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch @@ -123,7 +122,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() { } } - @OptIn(DelicateCoroutinesApi::class) private fun startWatcherJob() { watcherJob = CoroutineScope(SupervisorJob()).launch { val settings = settingsRepo.getAll(); @@ -151,13 +149,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() { is NetworkStatus.CapabilitiesChanged -> { isMobileDataConnected = true Timber.d("Mobile data capabilities changed") - if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled - && vpnService.getState() == Tunnel.State.DOWN) - startVPN() + if(!disconnecting && !connecting) { + if(!isWifiConnected && setting.isTunnelOnMobileDataEnabled + && vpnService.getState() == Tunnel.State.DOWN) + startVPN() + } } is NetworkStatus.Unavailable -> { isMobileDataConnected = false - if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN() + if(!disconnecting && !connecting) { + if(!isWifiConnected && vpnService.getState() == Tunnel.State.UP) stopVPN() + } Timber.d("Lost mobile data connection") } } @@ -178,7 +180,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() { Timber.d("Not connect and not disconnecting") val ssid = wifiService.getNetworkName(it.networkCapabilities); Timber.d("SSID: $ssid") - if ((setting.trustedNetworkSSIDs?.contains(ssid) == false) && vpnService.getState() == Tunnel.State.DOWN) { + if (!setting.trustedNetworkSSIDs.contains(ssid) && vpnService.getState() == Tunnel.State.DOWN) { Timber.d("Starting VPN Tunnel for untrusted network: $ssid") startVPN() } else if (!disconnecting && vpnService.getState() == Tunnel.State.UP && setting.trustedNetworkSSIDs.contains( diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt index 816bf3a..68a2853 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardTunnelService.kt @@ -1,9 +1,12 @@ package com.zaneschepke.wireguardautotunnel.service.foreground +import android.app.PendingIntent +import android.content.Intent import android.os.Bundle -import com.wireguard.android.backend.Tunnel import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import dagger.hilt.android.AndroidEntryPoint @@ -28,6 +31,8 @@ class WireGuardTunnelService : ForegroundService() { private lateinit var job : Job + private var tunnelName : String = "" + override fun startService(extras : Bundle?) { super.startService(extras) val tunnelConfigString = extras?.getString(getString(R.string.tunnel_extras_key)) @@ -36,10 +41,8 @@ class WireGuardTunnelService : ForegroundService() { if(tunnelConfigString != null) { try { val tunnelConfig = TunnelConfig.from(tunnelConfigString) - val state = vpnService.startTunnel(tunnelConfig) - if (state == Tunnel.State.UP) { - launchVpnConnectedNotification(tunnelConfig.name) - } + tunnelName = tunnelConfig.name + vpnService.startTunnel(tunnelConfig) } catch (e : Exception) { Timber.e("Problem starting tunnel: ${e.message}") stopService(extras) @@ -48,6 +51,34 @@ class WireGuardTunnelService : ForegroundService() { Timber.e("Tunnel config null") } } + CoroutineScope(job).launch { + var didShowConnected = false + var didShowFailedHandshakeNotification = false + vpnService.handshakeStatus.collect { + when(it) { + HandshakeStatus.NOT_STARTED -> { + } + HandshakeStatus.NEVER_CONNECTED -> { + if(!didShowFailedHandshakeNotification) { + launchVpnConnectionFailedNotification(getString(R.string.initial_connection_failure_message)) + didShowFailedHandshakeNotification = true + } + } + HandshakeStatus.HEALTHY -> { + if(!didShowConnected) { + launchVpnConnectedNotification() + didShowConnected = true + } + } + HandshakeStatus.UNHEALTHY -> { + if(!didShowFailedHandshakeNotification) { + launchVpnConnectionFailedNotification(getString(R.string.lost_connection_failure_message)) + didShowFailedHandshakeNotification = true + } + } + } + } + } } override fun stopService(extras : Bundle?) { @@ -59,7 +90,7 @@ class WireGuardTunnelService : ForegroundService() { stopSelf() } - private fun launchVpnConnectedNotification(tunnelName : String) { + private fun launchVpnConnectedNotification() { val notification = notificationService.createNotification( channelId = getString(R.string.vpn_channel_id), channelName = getString(R.string.vpn_channel_name), @@ -70,6 +101,22 @@ class WireGuardTunnelService : ForegroundService() { ) super.startForeground(foregroundId, notification) } + + private fun launchVpnConnectionFailedNotification(message : String) { + val notification = notificationService.createNotification( + channelId = getString(R.string.vpn_channel_id), + channelName = getString(R.string.vpn_channel_name), + action = PendingIntent.getBroadcast(this,0,Intent(this, NotificationActionReceiver::class.java),PendingIntent.FLAG_IMMUTABLE), + actionText = getString(R.string.restart), + title = getString(R.string.vpn_connection_failed), + onGoing = false, + showTimestamp = true, + description = message + ) + super.startForeground(foregroundId, notification) + } + + private fun cancelJob() { if(this::job.isInitialized) { job.cancel() diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt index cdf5151..f6f3745 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/network/BaseNetworkService.kt @@ -1,6 +1,5 @@ package com.zaneschepke.wireguardautotunnel.service.network -import android.app.Service import android.content.Context import android.net.ConnectivityManager import android.net.Network @@ -10,12 +9,10 @@ import android.net.wifi.SupplicantState import android.net.wifi.WifiInfo import android.net.wifi.WifiManager import android.os.Build -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.map -import javax.inject.Inject abstract class BaseNetworkService>(val context: Context, networkCapability : Int) : NetworkService { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt index cc8f0d9..fb3d476 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt @@ -2,12 +2,15 @@ package com.zaneschepke.wireguardautotunnel.service.notification import android.app.Notification import android.app.NotificationManager +import android.app.PendingIntent interface NotificationService { fun createNotification( channelId: String, channelName: String, title: String = "", + action: PendingIntent? = null, + actionText: String? = null, description: String, showTimestamp : Boolean = false, importance: Int = NotificationManager.IMPORTANCE_HIGH, diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt index bc29647..ad68582 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt @@ -20,13 +20,15 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val channelId: String, channelName: String, title: String, + action: PendingIntent?, + actionText: String?, description: String, showTimestamp: Boolean, importance: Int, vibration: Boolean, onGoing: Boolean, lights: Boolean - ) : Notification { + ): Notification { val channel = NotificationChannel( channelId, channelName, @@ -42,7 +44,12 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val notificationManager.createNotificationChannel(channel) val pendingIntent: PendingIntent = Intent(context, MainActivity::class.java).let { notificationIntent -> - PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE) + PendingIntent.getActivity( + context, + 0, + notificationIntent, + PendingIntent.FLAG_IMMUTABLE + ) } val builder: Notification.Builder = @@ -50,14 +57,21 @@ class WireGuardNotification @Inject constructor(@ApplicationContext private val context, channelId ) - - return builder - .setContentTitle(title) - .setContentText(description) - .setContentIntent(pendingIntent) - .setOngoing(onGoing) - .setShowWhen(showTimestamp) - .setSmallIcon(R.mipmap.ic_launcher_foreground) - .build() + return builder.let { + if(action != null && actionText != null) { + //TODO find a not deprecated way to do this + it.addAction( + Notification.Action.Builder(0, actionText, action) + .build()) + it.setAutoCancel(true) + } + it.setContentTitle(title) + .setContentText(description) + .setContentIntent(pendingIntent) + .setOngoing(onGoing) + .setShowWhen(showTimestamp) + .setSmallIcon(R.mipmap.ic_launcher_foreground) + .build() + } } } \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt new file mode 100644 index 0000000..c6bbf35 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/HandshakeStatus.kt @@ -0,0 +1,14 @@ +package com.zaneschepke.wireguardautotunnel.service.tunnel + +enum class HandshakeStatus { + HEALTHY, + UNHEALTHY, + NEVER_CONNECTED, + NOT_STARTED; + + companion object { + private const val WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC = 120 + const val UNHEALTHY_TIME_LIMIT_SEC = WG_TYPICAL_HANDSHAKE_INTERVAL_WHEN_HEALTHY_SEC + 60 + const val NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC = 30 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt index 91be50c..346a49a 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/VpnService.kt @@ -1,6 +1,8 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel +import com.wireguard.android.backend.Statistics import com.wireguard.android.backend.Tunnel +import com.wireguard.crypto.Key import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import kotlinx.coroutines.flow.SharedFlow @@ -9,5 +11,8 @@ interface VpnService : Tunnel { suspend fun stopTunnel() val state : SharedFlow val tunnelName : SharedFlow + val statistics : SharedFlow + val lastHandshake : SharedFlow> + val handshakeStatus : SharedFlow fun getState() : Tunnel.State } \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index def01b1..289af22 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -2,27 +2,51 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel import com.wireguard.android.backend.Backend import com.wireguard.android.backend.BackendException +import com.wireguard.android.backend.Statistics import com.wireguard.android.backend.Tunnel +import com.wireguard.crypto.Key import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig +import com.zaneschepke.wireguardautotunnel.util.NumberUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject -class WireGuardTunnel @Inject constructor(private val backend : Backend) : VpnService { +class WireGuardTunnel @Inject constructor(private val backend : Backend, +) : VpnService { private val _tunnelName = MutableStateFlow("") override val tunnelName get() = _tunnelName.asStateFlow() + private val _state = MutableSharedFlow( - replay = 1, - onBufferOverflow = BufferOverflow.SUSPEND, - extraBufferCapacity = 1) + replay = 1) + + private val _handshakeStatus = MutableSharedFlow(replay = 1, + onBufferOverflow = BufferOverflow.DROP_OLDEST) override val state get() = _state.asSharedFlow() + private val _statistics = MutableSharedFlow(replay = 1) + override val statistics get() = _statistics.asSharedFlow() + + private val _lastHandshake = MutableSharedFlow>(replay = 1) + override val lastHandshake get() = _lastHandshake.asSharedFlow() + + override val handshakeStatus: SharedFlow + get() = _handshakeStatus.asSharedFlow() + + private lateinit var statsJob : Job + + override suspend fun startTunnel(tunnelConfig: TunnelConfig) : Tunnel.State{ return try { if(getState() == Tunnel.State.UP && _tunnelName.value != tunnelConfig.name) { @@ -60,6 +84,46 @@ class WireGuardTunnel @Inject constructor(private val backend : Backend) : VpnSe } override fun onStateChange(state : Tunnel.State) { + val tunnel = this; _state.tryEmit(state) + if(state == Tunnel.State.UP) { + statsJob = CoroutineScope(Dispatchers.IO).launch { + val handshakeMap = HashMap() + var neverHadHandshakeCounter = 0 + while (true) { + val statistics = backend.getStatistics(tunnel) + _statistics.emit(statistics) + statistics.peers().forEach { + val handshakeEpoch = statistics.peer(it)?.latestHandshakeEpochMillis ?: 0L + handshakeMap[it] = handshakeEpoch + if(handshakeEpoch == 0L) { + if(neverHadHandshakeCounter >= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) { + _handshakeStatus.emit(HandshakeStatus.NEVER_CONNECTED) + } else { + _handshakeStatus.emit(HandshakeStatus.NOT_STARTED) + } + if(neverHadHandshakeCounter <= HandshakeStatus.NEVER_CONNECTED_TO_UNHEALTHY_TIME_LIMIT_SEC) { + neverHadHandshakeCounter++ + } + return@forEach + } + if(NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch) >= HandshakeStatus.UNHEALTHY_TIME_LIMIT_SEC) { + _handshakeStatus.emit(HandshakeStatus.UNHEALTHY) + } else { + _handshakeStatus.emit(HandshakeStatus.HEALTHY) + } + } + _lastHandshake.emit(handshakeMap) + delay(1000) + } + } + } + if(state == Tunnel.State.DOWN) { + if(this::statsJob.isInitialized) { + statsJob.cancel() + } + _handshakeStatus.tryEmit(HandshakeStatus.NOT_STARTED) + _lastHandshake.tryEmit(emptyMap()) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt index 5e59c86..4cce6a2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/MainActivity.kt @@ -14,7 +14,6 @@ import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.slideInHorizontally -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -34,6 +33,7 @@ import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.ui.common.PermissionRequestFailedScreen import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.screens.config.ConfigScreen +import com.zaneschepke.wireguardautotunnel.ui.screens.detail.DetailScreen import com.zaneschepke.wireguardautotunnel.ui.screens.main.MainScreen import com.zaneschepke.wireguardautotunnel.ui.screens.settings.SettingsScreen import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen @@ -44,7 +44,7 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : AppCompatActivity() { - @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class, + @OptIn(ExperimentalAnimationApi::class, ExperimentalPermissionsApi::class ) override fun onCreate(savedInstanceState: Bundle?) { @@ -164,6 +164,9 @@ class MainActivity : AppCompatActivity() { composable("${Routes.Config.name}/{id}", enterTransition = { fadeIn(animationSpec = tween(1000)) }) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"))} + composable("${Routes.Detail.name}/{id}", enterTransition = { + fadeIn(animationSpec = tween(1000)) + }) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Routes.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Routes.kt index a78fedd..6a5387c 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Routes.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/Routes.kt @@ -10,7 +10,8 @@ enum class Routes { Main, Settings, Support, - Config; + Config, + Detail; companion object { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt index e3d5203..2a9ce58 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/RowListItem.kt @@ -7,20 +7,24 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp @OptIn(ExperimentalFoundationApi::class) @Composable -fun RowListItem(text : String, onHold : () -> Unit, rowButton : @Composable() () -> Unit ) { +fun RowListItem(leadingIcon : ImageVector? = null, leadingIconColor : Color = Color.Gray, text : String, onHold : () -> Unit, onClick: () -> Unit, rowButton : @Composable() () -> Unit ) { Box( modifier = Modifier .combinedClickable( onClick = { - + onClick() }, onLongClick = { onHold() @@ -34,7 +38,17 @@ fun RowListItem(text : String, onHold : () -> Unit, rowButton : @Composable() () verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - Text(text) + Row(verticalAlignment = Alignment.CenterVertically,) { + if(leadingIcon != null) { + Icon( + leadingIcon, "status", + tint = leadingIconColor, + modifier = Modifier.padding(end = 10.dp).size(15.dp) + ) + } + Text(text) + } + rowButton() } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt index caae8b9..8d57350 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.toMutableStateList import androidx.lifecycle.ViewModel import com.zaneschepke.wireguardautotunnel.repository.Repository +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -18,7 +19,8 @@ import javax.inject.Inject @HiltViewModel class ConfigViewModel @Inject constructor(private val application : Application, - private val tunnelRepo : Repository) : ViewModel() { + private val tunnelRepo : Repository, + private val settingsRepo : Repository) : ViewModel() { private val _tunnel = MutableStateFlow(null) private val _tunnelName = MutableStateFlow("") @@ -127,6 +129,17 @@ class ConfigViewModel @Inject constructor(private val application : Application, wgQuick = wgQuick )?.let { tunnelRepo.save(it) + val settings = settingsRepo.getAll() + if(settings != null) { + val setting = settings[0] + if(setting.defaultTunnel != null) { + if(it.id == TunnelConfig.from(setting.defaultTunnel!!).id) { + settingsRepo.save(setting.copy( + defaultTunnel = it.toString() + )) + } + } + } } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt new file mode 100644 index 0000000..ca8bc06 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailScreen.kt @@ -0,0 +1,135 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.detail + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.util.NumberUtils +import java.time.Duration +import java.time.Instant + +@Composable +fun DetailScreen( + viewModel: DetailViewModel = hiltViewModel(), + padding: PaddingValues, + id : String? +) { + + val clipboardManager: ClipboardManager = LocalClipboardManager.current + val tunnelStats by viewModel.tunnelStats.collectAsStateWithLifecycle(null) + val tunnel by viewModel.tunnel.collectAsStateWithLifecycle(null) + val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle() + val lastHandshake by viewModel.lastHandshake.collectAsStateWithLifecycle(emptyMap()) + + LaunchedEffect(Unit) { + viewModel.getTunnelById(id) + } + + if(tunnel != null) { + val interfaceKey = tunnel?.`interface`?.keyPair?.publicKey?.toBase64().toString() + val addresses = tunnel?.`interface`?.addresses!!.joinToString() + val dnsServers = tunnel?.`interface`?.dnsServers!!.joinToString() + val optionalMtu = tunnel?.`interface`?.mtu + val mtu = if(optionalMtu?.isPresent == true) optionalMtu.get().toString() else "None" + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Top, + modifier = Modifier + .fillMaxSize() + .padding(padding) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 7.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + Column { + Text(stringResource(R.string.config_interface), fontWeight = FontWeight.Bold, fontSize = 20.sp) + Text(stringResource(R.string.name), fontStyle = FontStyle.Italic) + Text(text = tunnelName, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(tunnelName)) + }) + Text(stringResource(R.string.public_key), fontStyle = FontStyle.Italic) + Text(text = interfaceKey, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(interfaceKey)) + }) + Text(stringResource(R.string.addresses), fontStyle = FontStyle.Italic) + Text(text = addresses, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(addresses)) + }) + Text(stringResource(R.string.dns_servers), fontStyle = FontStyle.Italic) + Text(text = dnsServers, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(dnsServers)) + }) + Text(stringResource(R.string.mtu), fontStyle = FontStyle.Italic) + Text(text = mtu, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(mtu)) + }) + Box(modifier = Modifier.padding(10.dp)) + tunnel?.peers?.forEach{ + val peerKey = it.publicKey.toBase64().toString() + val allowedIps = it.allowedIps.joinToString() + val endpoint = if(it.endpoint.isPresent) it.endpoint.get().toString() else "None" + Text(stringResource(R.string.peer), fontWeight = FontWeight.Bold, fontSize = 20.sp) + Text(stringResource(R.string.public_key), fontStyle = FontStyle.Italic) + Text(text = peerKey, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(peerKey)) + }) + Text(stringResource(id = R.string.allowed_ips), fontStyle = FontStyle.Italic) + Text(text = allowedIps, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(allowedIps)) + }) + Text(stringResource(R.string.endpoint), fontStyle = FontStyle.Italic) + Text(text = endpoint, modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(endpoint)) + }) + if (tunnelStats != null) { + val totalRx = tunnelStats?.totalRx() ?: 0 + val totalTx = tunnelStats?.totalTx() ?: 0 + if((totalRx + totalTx != 0L)) { + val rxKB = NumberUtils.bytesToKB(tunnelStats!!.totalRx()) + val txKB = NumberUtils.bytesToKB(tunnelStats!!.totalTx()) + Text(stringResource(R.string.transfer), fontStyle = FontStyle.Italic) + Text("rx: ${NumberUtils.formatDecimalTwoPlaces(rxKB)} KB tx: ${NumberUtils.formatDecimalTwoPlaces(txKB)} KB") + Text(stringResource(R.string.last_handshake), fontStyle = FontStyle.Italic) + val handshakeEpoch = lastHandshake[it.publicKey] + if(handshakeEpoch != null) { + if(handshakeEpoch == 0L) { + Text("Never") + } else { + val time = Instant.ofEpochMilli(handshakeEpoch) + Text("${Duration.between(time, Instant.now()).seconds} seconds ago") + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt new file mode 100644 index 0000000..59c5698 --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/detail/DetailViewModel.kt @@ -0,0 +1,45 @@ +package com.zaneschepke.wireguardautotunnel.ui.screens.detail + +import androidx.lifecycle.ViewModel +import com.wireguard.config.Config +import com.zaneschepke.wireguardautotunnel.repository.Repository +import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService +import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import timber.log.Timber +import javax.inject.Inject + +@HiltViewModel +class DetailViewModel @Inject constructor(private val tunnelRepo : Repository, private val vpnService : VpnService + +) : ViewModel() { + + private val _tunnel = MutableStateFlow(null) + val tunnel get() = _tunnel.asStateFlow() + + private val _tunnelName = MutableStateFlow("") + val tunnelName = _tunnelName.asStateFlow() + val tunnelStats get() = vpnService.statistics + val lastHandshake get() = vpnService.lastHandshake + + private var config : TunnelConfig? = null + + suspend fun getTunnelById(id : String?) : TunnelConfig? { + return try { + if(id != null) { + config = tunnelRepo.getById(id.toLong()) + if (config != null) { + _tunnel.emit(TunnelConfig.configFromQuick(config!!.wgQuick)) + _tunnelName.emit(config!!.name) + } + return config + } + return null + } catch (e : Exception) { + Timber.e(e.message) + null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt index 8ed00be..7a31bea 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.FileOpen import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.rounded.Add +import androidx.compose.material.icons.rounded.Circle import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Edit import androidx.compose.material3.Divider @@ -58,16 +59,22 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavController import com.wireguard.android.backend.Tunnel import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig import com.zaneschepke.wireguardautotunnel.ui.Routes import com.zaneschepke.wireguardautotunnel.ui.common.RowListItem +import com.zaneschepke.wireguardautotunnel.ui.theme.brickRed +import com.zaneschepke.wireguardautotunnel.ui.theme.mint +import com.zaneschepke.wireguardautotunnel.ui.theme.pinkRed import kotlinx.coroutines.launch @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @OptIn(ExperimentalMaterial3Api::class) @Composable -fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValues, - snackbarHostState : SnackbarHostState, navController: NavController) { +fun MainScreen( + viewModel: MainViewModel = hiltViewModel(), padding: PaddingValues, + snackbarHostState: SnackbarHostState, navController: NavController +) { val haptic = LocalHapticFeedback.current val context = LocalContext.current @@ -76,12 +83,12 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu val sheetState = rememberModalBottomSheetState() var showBottomSheet by remember { mutableStateOf(false) } val tunnels by viewModel.tunnels.collectAsStateWithLifecycle(mutableListOf()) + val handshakeStatus by viewModel.handshakeStatus.collectAsStateWithLifecycle(HandshakeStatus.NOT_STARTED) val viewState = viewModel.viewState.collectAsStateWithLifecycle() var selectedTunnel by remember { mutableStateOf(null) } val state by viewModel.state.collectAsStateWithLifecycle(Tunnel.State.DOWN) val tunnelName by viewModel.tunnelName.collectAsStateWithLifecycle("") - LaunchedEffect(viewState.value) { if (viewState.value.showSnackbarMessage) { val result = snackbarHostState.showSnackbar( @@ -156,8 +163,15 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu } .padding(10.dp) ) { - Icon(Icons.Filled.FileOpen, contentDescription = stringResource(id = R.string.open_file), modifier = Modifier.padding(10.dp)) - Text(stringResource(id = R.string.add_from_file), modifier = Modifier.padding(10.dp)) + Icon( + Icons.Filled.FileOpen, + contentDescription = stringResource(id = R.string.open_file), + modifier = Modifier.padding(10.dp) + ) + Text( + stringResource(id = R.string.add_from_file), + modifier = Modifier.padding(10.dp) + ) } Divider() Row(modifier = Modifier @@ -170,8 +184,15 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu } .padding(10.dp) ) { - Icon(Icons.Filled.QrCode, contentDescription = stringResource(id = R.string.qr_scan), modifier = Modifier.padding(10.dp)) - Text(stringResource(id = R.string.add_from_qr), modifier = Modifier.padding(10.dp)) + Icon( + Icons.Filled.QrCode, + contentDescription = stringResource(id = R.string.qr_scan), + modifier = Modifier.padding(10.dp) + ) + Text( + stringResource(id = R.string.add_from_qr), + modifier = Modifier.padding(10.dp) + ) } } } @@ -185,36 +206,49 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), padding : PaddingValu LazyColumn(modifier = Modifier.fillMaxSize()) { items(tunnels.toList()) { tunnel -> - RowListItem(text = tunnel.name, onHold = { - if (state == Tunnel.State.UP && tunnel.name == tunnelName) { - scope.launch { - viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel)) + RowListItem(leadingIcon = Icons.Rounded.Circle, + leadingIconColor = when (handshakeStatus) { + HandshakeStatus.HEALTHY -> mint + HandshakeStatus.UNHEALTHY -> brickRed + HandshakeStatus.NOT_STARTED -> Color.Gray + HandshakeStatus.NEVER_CONNECTED -> brickRed + }, + text = tunnel.name, + onHold = { + if (state == Tunnel.State.UP && tunnel.name == tunnelName) { + scope.launch { + viewModel.showSnackBarMessage(context.resources.getString(R.string.turn_off_tunnel)) + } + return@RowListItem } - return@RowListItem - } - haptic.performHapticFeedback(HapticFeedbackType.LongPress) - selectedTunnel = tunnel; - }, rowButton = { - if (tunnel.id == selectedTunnel?.id) { - Row() { - IconButton(onClick = { - navController.navigate("${Routes.Config.name}/${selectedTunnel?.id}") - }) { - Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit)) - } - IconButton(onClick = { viewModel.onDelete(tunnel) }) { - Icon(Icons.Rounded.Delete, stringResource(id = R.string.delete)) + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + selectedTunnel = tunnel; + }, + onClick = { navController.navigate("${Routes.Detail.name}/${tunnel.id}") }, + rowButton = { + if (tunnel.id == selectedTunnel?.id) { + Row() { + IconButton(onClick = { + navController.navigate("${Routes.Config.name}/${selectedTunnel?.id}") + }) { + Icon(Icons.Rounded.Edit, stringResource(id = R.string.edit)) + } + IconButton(onClick = { viewModel.onDelete(tunnel) }) { + Icon( + Icons.Rounded.Delete, + stringResource(id = R.string.delete) + ) + } } + } else { + Switch( + checked = (state == Tunnel.State.UP && tunnel.name == tunnelName), + onCheckedChange = { checked -> + if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop() + } + ) } - } else { - Switch( - checked = (state == Tunnel.State.UP && tunnel.name == tunnelName), - onCheckedChange = { checked -> - if (checked) viewModel.onTunnelStart(tunnel) else viewModel.onTunnelStop() - } - ) - } - }) + }) } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt index 91ea6cf..1400cf1 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt @@ -42,6 +42,8 @@ class MainViewModel @Inject constructor(private val application : Application, val viewState get() = _viewState.asStateFlow() val tunnels get() = tunnelRepo.itemFlow val state get() = vpnService.state + + val handshakeStatus get() = vpnService.handshakeStatus val tunnelName get() = vpnService.tunnelName private val _settings = MutableStateFlow(Settings()) val settings get() = _settings.asStateFlow() @@ -102,33 +104,34 @@ class MainViewModel @Inject constructor(private val application : Application, suspend fun onTunnelQRSelected() { codeScanner.scan().collect { - Timber.d(it) if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.config_validation))) { tunnelRepo.save(TunnelConfig(name = defaultConfigName(), wgQuick = it)) + } else if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.barcode_downloading))) { + showSnackBarMessage(application.resources.getString(R.string.barcode_downloading_message)) } else { - showSnackBarMessage("Invalid QR code. Try again.") + showSnackBarMessage(application.resources.getString(R.string.barcode_error)) } } } fun onTunnelFileSelected(uri : Uri) { - val fileName = getFileName(application.applicationContext, uri) - val extension = getFileExtensionFromFileName(fileName) - if(extension != ".conf") { - viewModelScope.launch { - showSnackBarMessage(application.resources.getString(R.string.file_extension_message)) - } - return - } - val stream = application.applicationContext.contentResolver.openInputStream(uri) - stream ?: return - val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8) try { - val config = Config.parse(bufferReader) - val tunnelName = getNameFromFileName(fileName) - viewModelScope.launch { - tunnelRepo.save(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString())) + val fileName = getFileName(application.applicationContext, uri) + val extension = getFileExtensionFromFileName(fileName) + if(extension != ".conf") { + viewModelScope.launch { + showSnackBarMessage(application.resources.getString(R.string.file_extension_message)) + } + return } + val stream = application.applicationContext.contentResolver.openInputStream(uri) + stream ?: return + val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8) + val config = Config.parse(bufferReader) + val tunnelName = getNameFromFileName(fileName) + viewModelScope.launch { + tunnelRepo.save(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString())) + } stream.close() } catch(_: BadConfigException) { viewModelScope.launch { @@ -177,6 +180,10 @@ class MainViewModel @Inject constructor(private val application : Application, } private fun getFileExtensionFromFileName(fileName : String) : String { - return fileName.substring(fileName.lastIndexOf('.')) + return try { + fileName.substring(fileName.lastIndexOf('.')) + } catch (e : Exception) { + "" + } } } \ No newline at end of file diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt index ca25516..b886444 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/theme/Color.kt @@ -9,4 +9,9 @@ val virdigris = Color(0xFF5BC0BE) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFFFFFFFF) \ No newline at end of file +val Pink40 = Color(0xFFFFFFFF) + +//status colors +val brickRed = Color(0xFFCE4257) +val pinkRed = Color(0xFFEF476F) +val mint = Color(0xFF52B788) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt new file mode 100644 index 0000000..7cdeddc --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/util/NumberUtils.kt @@ -0,0 +1,25 @@ +package com.zaneschepke.wireguardautotunnel.util + +import java.math.BigDecimal +import java.text.DecimalFormat +import java.time.Duration +import java.time.Instant + +object NumberUtils { + + private const val BYTES_IN_KB = 1024L + + fun bytesToKB(bytes : Long) : BigDecimal { + return bytes.toBigDecimal().divide(BYTES_IN_KB.toBigDecimal()) + } + + fun formatDecimalTwoPlaces(bigDecimal: BigDecimal) : String { + val df = DecimalFormat("#.##") + return df.format(bigDecimal) + } + + fun getSecondsBetweenTimestampAndNow(epoch : Long) : Long { + val time = Instant.ofEpochMilli(epoch) + return Duration.between(time, Instant.now()).seconds + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1879d0b..030e873 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,4 +58,23 @@ Turn on Map Bad config. Please try again. + Interface + Public key + Waiting for the Barcode UI module to be downloaded. + Barcode module downloading. Try again. + Invalid QR code. Try again. + Addresses + DNS servers + MTU + Peer + Allowed IPs + Endpoint + Transfer + Last handshake + Name + Restart Tunnel + VPN Connection Failed + Failed connection to - + Attempting to connect to server after 30 seconds of no response. + Attempting to reconnect to server after more than one minute of no response. \ No newline at end of file diff --git a/asset/main_screen.png b/asset/main_screen.png index 09b9a2e1b417c42fb1dce02cd9987477fb84964b..2b3c6262b90b690e4cd18cb1dec17b4c575ef9a3 100644 GIT binary patch literal 43591 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 literal 42320 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 PTCS|1Azvi-GWdT1HBeo* diff --git a/asset/main_screen_old.png b/asset/main_screen_old.png new file mode 100644 index 0000000000000000000000000000000000000000..09b9a2e1b417c42fb1dce02cd9987477fb84964b GIT binary patch literal 42320 zcmeFaXH-*N*Df5pSU^EV`i+7J2#5#>2#A36qBQ9O(o5(_ODqV|L^{%?_ki>orT5-j zkls59goJ!6aKF!Y-tpBRXN>dXn4z$D*x7rnHQP0>IromQvZB=0OVpPj5Xe;-X^B@5 z$k`_l2+5m^r@=eZ@2-7>Ku+B=el267pa5Y5Z(W3(I|YH90b8et2c9~0vQ6>^Y;!}1 zAG!_x%leo@&JvFS{|!%(|7RQQLLg!`Z|$MxZ~sw!`ACfO5zj;3hrFB*A3lOW&V4&` ze1;p~pp=l{S56e)RAWkKjXI!ACs*fDLXQ z!AB1v0*2g%Jp4um90J_@0vwOu^6+y!5isE9c=UvykJnJ(;S&=hUI@p-$AS+Zf_eS7 zx&3E8r%33zILl2YMJqx0nK~UUs|F+qJoNiFaz-Id*&7EP4SITNkhl7k{tiY zXz?1wa=zKIFols3N|f*Aj_Y}Wwk*Enc3d5pJE7a@@A%D%t5+x65B@CqHu@QUzI`Ev z{Fd<*&L6SoYlf5Rm`Ny;&b*YDkfY*{W+=>@9DIS{_t6IvHC@x-34{MV9Uo-3-t5Z`TJ?S3p1u~;oLveEtRu7!nIV?_U%d4vYVpK zKHXWd!k_9JQOJvaaO4fv^NhoD8P$syFOL0Dnu0+7fyhXRslJO{opA98pPLZf483-d z>D#r_cHf#+HNSqmOj*R1F<@~BSyw-2YMuSWVnwg`>x-2f=?My*+(AG83ct(oSywbq zlS_!dCb{eN3Lq@FgQjP-Z-^Er|^CIPs)&9 z!Y+0NoR#`VRdgUNuVH^i@Lj3cZyBVejuh+C{pX^C`uqE@((pZYx*6Ef%>{X%#{X{f z)J#)Q%%eHHC8E;JpEb1ts_Q;6ppsT43d5C+!m}9zXnF65643V^B%TvGm<^ruuq=H!ZKU&>HJk%Zu@$X!z-I_%!ciq;FBfh!+n2}gLF%C!h#^my{NZG+!U_ceisj4aSYr^25<^D378 z67XG0+Qk+(QR0%44JZP7yLd=fzbGdI#tVUPyngYbQPPNumS?^_3O&S9&28KoHgs>` zaUwfE7pD17-FY(lr&jydxfuc(2;FoCF1IKtS!$-2C6IC>5s8n0G%4qL|f@ znXf`S7f5kpICE(A9=gGZOS8s95+@b(6x(g!gU?vrERDpkXQs#JSyFYVE{Y>zcGxZ! z5sa0Zmh@E1*reJ5XI#R&>7L-^7pgI@?gUG}dev*!M3^mKxVKt1^L0#*9MT=KyMcoC zrf4Lq$jZpHfLWk7N*5M;Ou?L#JNgta-J%QtvjcDJf%n0mBf`Q|Js+F2e-FJ^t#z=T z`h(fFekfuxfeGiI9_QS8H%O!?HH_R>!g<)hr*h+H=QAZf4YsGs4n&k3B&=gC>nm}` zhYp>5p(#jC#y3tFww%c#l^#YWvX4QBzPiJBA$)aJku0E+&`5*W=>8#Ys6_uTk-*)A zE3?=OaC31QO4t~7KnRz*mFrczO}Y?-wS})jhGD=mri*0|>cW9xVPOGu?lQ<4_t_!5 zg0!8T9a#JetMKmOf=JbdaA2@Q_o@Z$`@ftW!h8D0MfN^%up*_iZyp%Zxye9c$i&R7 z`|b|{jxJ2Nw_L$L=_rm_*A93QEd3Vci7B7$^-tE`-rnY{UJc2r18U3;gBvF;o8LNp zl}+567Q5XB7s4(kle^ois}E8riJ-fNj_e5f>zM=?qrGe*kXi24ByQg4>XI+UTSJ; zI=W}8=fd0%j59Mc_sV1g)+Z{-XwazZZYx6w27VjdusU?U@@LFT~bYL#lR(d<4~1d6s726MF&+$RMb z<~wt-sLz5gXoXN$jS0P*`MSDKr808^QSmGhUeN<3;Dt9>f4&^&Q>4 z0x5dK-Gv#_b;s{-kuhiOfTit8?xF^!XPJj56tc1*iUV2i>$QtEw0|!I1|Bmkrt{H!U4*ZA zV5-j^VN%L3a%RMOY|BvU*uunPX?-O~1!kwBA`I%jX5chR6?IcnQmQbMhjcqUKv z;T%Ky@R{VGr|Jhg(Y%&~cRL)w`=!Ub*$-&A?Ja8_3Z8l&VX-+ySoeCgR6Im8z2tz~ z_aS{U_5P0(X?BI7;U`&nSt=oPNY(iG`AckSVEr-|DRXD9UoA*D=AIR3wcQuY%*eZqmzKVBS&P*p@-~@_^SFc}3 zeG9!ybU&};<+5kh_ZG9Ns&L*bP_SN%k-BaNEDZ2R@FNX1*3{O+jcNwZsE7!4aRbON zXTw!V%C1;}?Ck|GG0QJMH1A3UX2`Pgn08XT_cZV(=nr)oy^^ifACiySZ=e47Ih>HG zbzyZ+2BE=0rtJD$IO3u|z06Ht2@%4ye^%HleY9-R2?9wcj5J^(?f3llCYLu%k$TwHMMk|_*b7~Dkfpo2?~6Ev1&xJ4OR zpKu0Z<@4g}lOKt1WahdK4oX>^$q$I&;hgWskIW?FEF!)4SyQzd8X8Dk_llg?ForVn z(ivgjgRgs*rM*?)5F+4jBK_&nqSm7X^%_4Q0{>eaSr$sNvVxc@hb&ShfjQ(E+dIL{ zvE>eM5D$D_SPyTn3vCGRcGnf>{P;}5o#AmbvOv^(5D+PYs%L9a0f7lIdK;k#SV6chLAg|` z4}pE{%y(+5m#v1#HL4fqXcaH}vFBweoZ@(8>!8Xu0dqep>{8SLNX}3R010T2@cjN= zZ@S>fn8Oj%godio>=mcvIq;fw1gp2S)^}ldLv-2eC3om6y>~|q6=)Jjkt&Z#d)0jO z3R}V<&!b0;K@9vBv%X`u+1L(S_K}pQhjbh!0;ot1h0H0^!X&B{!FixH2ZO64g)SgW zwAK|B6*bYj&-O?$xD)VXt-zH)3HZ*ZI{~Y!1X42X3dg0(Tvqt&dD^8mrg6ga?v!pb zxh^PI#Io&NYk1dFon!XAQ2eR?Kwl7#I@X-Q3S=sT&!I zYy4VmYvrWI`=1Vf#k;LcEfB zIC-4j(H_EO+Efgx@thVrP+Nr_P4VZXt=!vzykClXu6vhtjOC{Uh5!j$^{?||RjmMk z6LNACw=88L5FPgpC;TBMTW1WDR6xnec|Swy%54FQdVqRVkDfZr=U5`jIbY849)p*? zv4H}U7M3%*zOJ7T4-YL6sagKzsB=0-lLaw zY@xy)^6V;GshJ0U5AM9!%}DO6H#~aIMSWmb@{rF+UT#pp} zAutom{Z1;QF6zg`l))FL)eFw4%UzV?e>-#emPGihGJPr+h;X4am z2@egX0c^JKXehn?QP!|MdT(webn=VDo!qga_1;uDV9`m0bBm)+3!-zVqrMqJ`qHJd zkke}UWp8G`Pyo%eU?Oj6ZjO4m_5Jq+TAiDGSj+~$;p~^J78YQD9yZesYBFitxGWNK zkUS~pm;J-2HsL(R!4kiVN)4nDO_eCY3C<;9zXp#1yUQ!vcHw4CePj1~+;yk5t<8OR zv8U1nk%a~shdyCm)Y9;GUi!f$fV<-!RRM(zilqy}`>w@W4_Pq6x z!X`SGc`2yy_lSsym@%j3Z{kEisP_~dGS_CgSDUU$p-Mi}!jj;&-IAU~dbqo^<0Cqr zzX3Z`FJ3L|N=!J~-KW&|$Yjkr1Nq2j)Yx8N#FeRa)~dGN^eZjQ5#$xcLwa94z-8Xcy(BgytrNCBCB!hE%Xlt0D%HFpiZycjks#=^YRui=clC1 zHqsnz_Nypq6w^BO2Lq7msi>$pXJBu?of@fEm6~XO#MQOZlkD!;hQ|<6`9e30#^3c_ zMbYd!jU@n>cG!#3%NS1;nFLA1;_t1Q7lBU;?D<=6Syjf!CxnF9_rH{icS1+uF(|~J zyzRM2{doJ`%87UVJyLw*H&4Acv7Yu?$htR(*+6ZqyB|F@nC8(QErrcyTn9;y z14bC0=hnA9gcOO*cyRqW5%UB4is9bbq2duG&S(+shEyCePmeRCC*%-=tGxWH&0S$eDt#VU>hlau@Qp=G)bI8Lr zfD~s$wU97;HsXkL*oWRt+QPzOWiU5gDd9T2 z69BR#vawPnMAK*BXPXvzx}x{T3&Z{25A*^Hf05v-{ydLfQFbH*2my#jGwx>+nVg(l zsiR5zW-gjeus$|_#ip{cYSoQUjnQ|vex)l{v6M!W+5IlU!Ez4q!g$cv(Tj0{CeMc39qiM??{G%I9*dJrwrKj3yglRaN64|7%Q$ zh?_9SOt|>luznqLoCA2E#BwNaIlro^YQEujmQ$yIDc!rNsjNB>84K-cwQPp+)B*vd zcSDc(s`(H+_KFwjE$Hb7Ph07WF(2t40*~FFoDemlt6Sk%zSHIM*4UFPjes4Zyh10) zRAG#c71%4g1Ah+xd7l6n4s zgO_)&WXvYeSDbLrW!mF&f&R%86Q3pxy@RIy%)VvS_Pok9Y|Lf2uG4GPBHXfNUM{Zu z!Llo_fPjE79w}}=T4GHzAp;V)IL^^kD}8a~J*9XLkV9(FsjI3IEoQ*>C;~Ubfar|l zaT%I$&b6QtbZpj!B99trps?!2m4NnuPo#pjh-$PSevln8h_HgZsoZXgfi6vCYYZsv zXcIFCVumpWD>n&#bx+>IgvR~#&)FCy(Ku@11Q-gBj@!j%BdWHSbH4NNTaS(d1r|Np z>JfHFe>obQMo=R0=-I^xIZi5?>R($RU4bGwka-QqU?u5A*o1vsuG)&M)W%L!SeWCw z>w>DnGe6kZK#4oVOmW32CUT8EW8873V`O9m#Bw=VSvC$1d>ft&x0Z?=LSzdOJcfXq z4hOu$0Ej4&p-Lj=atlb1ECIx&j?X&>v4S=R2pr>% zCYmWo>P-U$781{p3Gam^Y?N+Hxdq(I*~TENahr8P{oAvjFH?T4UJP;5zYG~BrSt4B z(56nVF{txS4ia=8i1FYaow|2mbstmekO5b=oCi*T@4NlSIu&Mn=X5WSQ+eU0)PgLB{e! zOY{q${+NbxX3@K?X_rY?7l1X#hP!w*^fBpUJINP>)K~nu(h?ldt|-{y8lN61WH|2z zB_$c%cCv*iwcxDOn8=5k@{}-xIv?g)C)v~sO@YV*veNno9n{d7#z62dNORZq*ZU*7 zM>VCVhtH5bEfciK^6Ws1zCmUge`aT6v#B`<0-Og}Anc0Ga4#$}Jl=i%pozgf39R2& z;`))eEX29s-CKPq_4ZfJkkQf#YZdf@xq&Re>L_8|Z0pebG~_DComxQJWeWhF1b;mI z=-jz;awP=|qbXsdh;ts#rawwlolOgq#h?h0L_EF?boiS%=~p-`J0l}X&}lTmrV9>m zxuHn3h-V|254|&yli2GI<>BE$;_T6%iuKTmtwup2A}*cdG&WT>z{9}Tie$4qkwnGZs7V>m)22O?UIVEGo#$@wg!^uD>+(X;uf>I0{7<%sik-96@U%1e4?(&M~MAbOIR} z%<$Mf;iVJ`cl7G_5pJV{?VE+jSp00LQ#^T}2MMSP{BXDK%|4FGM)SdSL6ePn)QWtPq_i{{^Wn>;=|vv%>SAS)Jf6jS#s-A zNa0RrIf&~^!`#{8hiL1u5=($#WMyUT?Roa^9?f^ew1HP^tE}8mBuMEj23n2VB0P5# z#QyxOP2=SF9b#YUY`IJgfg~C@Er(Sf+IUv1^$N4Fq=Pdl=qgT!M?~DZb&C`0_GeW< zA7m0$nQbxe?vREzT^=pAjH!sgQhhUB5s*RJEI*I##U1RVDIPlhNjj&XEA2_F<`vxsf189Of0yez#8iA!zVU!{ ze{qHtq+*uK&zE0-l3wEH=A{sxx6MIrjvGDwpS{d}*Ph#`+HvNIcHjb~!ESO|blu(a zS4Yz$SP)vpS!HEqfU5xjHZ?WXn5LC40yhMWD>RL-nORkDmsf=|WV3Pr%K}sk1 z_U&88)!~8&{RF|izS4Whp2VB?{6HP-*-k`cj7$BWx>7-17&zDG&!5{OIJ9d#Tnn3- ziX#E178f@GVmH_TCyG|$WYa=7#)RIYd|(qhx~xDPH5HA=FEJll53`O1PlSF#Z` z(fm+7zycbn-;4$c;$*cuD8D$!v4Hh`=Ul4-AfrWlmPMx8ix<|WlMu+Wi@kk)N&-to zbS~qWqe_=AUrtwuL3i+@it>H$@8!Gh%qJx!0jFQ0Tj{*Cw6w(nR&KRq#Rfp0NkI6| zL~8I5HDjJhSQwu|q1e*7uCta7GTta&OLc(t?rPj5lF$I)q~+v1U}0f7>MJVRove22 zir+Cd)08AG*ofIc)&MI@oVv9vD<|jCZl4nJaJ{k-k)`>%+Re$pz@X_Xor;Qzk%+|* zXRc{bd;)w+Q&Lhgv|pX*mEW7pq);g3I@b@Cf?y9a*|3NRKA22Lv-ee+OP4NDh$;<(OK%G(C$y+PtgO@H}1{4?_GQl&l zQlP8v%^Gn`_}5R>RmA(!=WP(}#| z2-w}-1=Y|nb%4fFJX8+AQV-_oYU}7!88-L)G`gpC>l0JYOpL`Jop?As;W z$Hc5g4m9?nDnhT?-Erf3-|N?}%glO`te5!M+1dH|T>z$)mC2kpcmmKTcnJ$v+cRK5 zl?qtgts6ITbjt0uIjMgFw#jL!m)Qh`r3c7ldBFV$=c=FcUV3_Zz|Z>AUIFJG%u>tF$;nx41Pj6}7m=e?oh2jt z=sP{+{j~<@#%n!25vZQ$(J#~waUYPkWJm=KzkIvboh%hZ+!Oa91!YY$;Hx$C^yvFu zuic9Y6bIJu&l7Mh;2FP~>y*7Qz=iIrJFSnqZchE)5~3N@HZhr>`uz#WAvS~b%u7AJ z8r$EsQH>yf96F@y=X)e|E{h+qtfYdAiHi|SJSI5&Tx3iZJwU%##@&@YjD+XAfe2lZ zo)~{7=A^Xypl*J+R*7m#@EEW4{=lJCl!3`BIIsmCe)4c?qLJ%`@`vTM$@DHe(E}Mr zIp8`#1BMxG<$8s-yquWWZ||R1ot9I;OwL>&m47FDKnS@5wqhR{g95BfolYW&)%`JL zW#w-%F*FlpD|waHQetzFpit8vEH${Nt$VbAAN`q>1l&VH;O*eWirSwTJ@<)sbyo*q z{oFi-PAh}lfuK%4T=dyG$fj(oF-YX-$?v^WkjDXvaX-NtkoT3nZ3(GB7Xk`Zy%J)V z1lX}vz}GSmxm4YxEe_3}O!qPz*T+*;(pPV?NATM-D^REk7C}+QG%xm?md4A0OZu?q zl?kroR3Qhx*)LQ6Fqq2;ax7jq!ijc~!y$m{QS!fz0!2QqztIGOWer#OeYr#GDAN}A=v(x|X% zK=Td@vzYET0OD3DfFj%+pdvfk zt8bKjlZUd(ZgzgYBr5SI2pwwK>Q1Y}+!b~UaSk*B8D1j0E8A_H{xYoW>UmXr=rN*h zLa0?EISW=iZ&FeYW%|T9kFwg$o%QVTrhs?Nz`3q+EPARciY(QiL8C+~Qi+-F;X0@~ zn0M&6w{?J|(bFBh*z-gD5G2eX!$Z6-w_olDK1w0`kM`je>i#_4%B(tI`~(lQ=3G}L zdR_Q!^EWz|VMY!P>yBvNo%t@pkkCfI)2ks)SJ&{kw9TMzJYDEH9rNct! z)e5Gq2%wHY`N|ts3Ag{9%Xs2X%Z*8+PFlLYee`S#0fD0~QHuWndWn=B8<7D{) zI8+V}Si@0VA+1NZm?>@=q*HtTh~asi@!F)}7egQ=}LtSR9o|{-2rH z9~eibrl(7l635EhZUs!feAy30#=!S)`w!LHT zTFK4(CzXQpD4zf5{50eFaIrHN;vH*AxC06A_6ADtDNXijDBk8>?*2sPYyNha zMr}*BoSE~p*Va1EJd-0At$S;9&^QVg=H=!6G1o6fp(prfdc$?53rAwJr~z!9t9pMr zs=fqJB|;k&+1^n6ZrO~X)@1zCgR~f^UZIHs6ZsNg@B)YQMS)H&Qha@a+dL#}N9GRl zth8|@!2f*y&SRREl7gGMAo}E>p27vJ)*=NKvoZvn%zmknu-opFxs*^N>V@{W!&wz2 zPxQe8-Ds&O7`W1tKVCT*PAKP5^Yl=~r(0ZPPxA+|KB{euh5yPc=@KG?aJ;~^_X_4J z#P(*j#6x2?dr2t&Rk5;40d(zUQ2Mw&0X!V2E5VFsGt|=+;|yzlnsh{G5qVVgoY!`B z3j7N1=D+jhYHP&xIW5HP*gIIpOAC881~_yIApn26Z421W1o+XxzTFIf%4$xEAd;ac zEfHj**!%O9EOEBa<;Su1=vCtJ+rQn+nI4(0kArBlf8@RqNblu5n{Uv8v2)e7(KuZ> zO1kkB@~n+QU75;$@dv1g%gfK~aMirtaj*u!p#7t2wnnZW2_N8wbYCf73pEAc#e$U0 z1?8YLP-vg}9mI_|eni)Lr4+w!>B!9zkF$j!eg3n2k`kEO(HPXpA`mWgNDrcvU5!^4Ow3t+t*`fJbj4` zM6-(hB!5GjR)F=^%Q2JDNC39pZFpj=iwS#nMqVX?*9`}j2etyYD6nHq9D^kw_!@h{ zxw<|w%N@JANf82WXR&Ecn&}Zx3lnUa&i^1a8IWPLPkk9=&E*fX&X^nGw;JwN6*I2$ z9=%0X1Ku^#GCcY+eh~~LQqRM#oyiOMka2XnXaiKLObtNp)ggrbP@ZmIZ|`WawKf$D zySGN;pk)oZ97?&@Bl`u{^}U5L!*`GT#eJ^$LRa_k>C4joV}nI}rR*PciKaIm9vch+JZq9fX^}2Y-p}-WrP){{wj6(p6U}p+B7JJ=3Qf89KObfFY}UEDgt+GMjk`;I47d67&~dZ^ z9{ZBW0|i_{OH6&q>iCF_B~6v`8Q@%_D?@lK1znCNANKRV4F$00L9qyve&QNu!7p)-Sd&zB|;t zD>g@&9#^s$V$#4XyF++BYPdF%qRWq)%}2+dF-12)jn)+VPTSzK*)SHD240kyM_ zk`uR_PV5EMoxU08FNE1XB?iKgeH|~2^Zp<1wth}5c4ddFEYUkxgIWvc54dqSA*7Ad zGQ@ECN9JJbuyY5#^9%&#K~d>00kXoCqUUyEVq!BF!aq{;SDWdDgoe&6sMHhmsH*~A z)aVs~{1>I`tT~Dqx(GnkUak|>E@+`af6-oC+T)-6qQq)FecqclmMcvu$b7^hNZ6wA z0aSdf$XrEU&LU=*nGQ4>cCAm)q{h}?MuY1XtCr>DKeo~EU0q!r#pK)1wbs_wdfh6% z30MMl4L)>p20$&PAOX7^$-B%E_}E%mU}M|?)Kn?@_9N|PYV@kg6!8FJG#2G#tt0P) z%sfw}bZk0QNi^`F4+gOPB2VXV?*Tv<0t>5!G&On6Sf>?3EnqEFaIGEP4E|vt+I~Hc z_KXhio}T%xgoHBtkCi&&{>ZUFS{xg%rt*kOg6E4sTB^SNb%5$~WlC?iZ%{hnw(>naLBwQt(L2w=b-X!aUub7%+0W`l zZ52Q^YaCcN19>@J4O+c&`?-(jE|Y7#RJirvb$5tfxmb4AtlSOR zFL+(KQ889;nviMC0zYUw167CzF>M<4ixxI)06Ju0M-7x32)zje6ve>c>4>|>0b1K& z;rVCXb_<%k$!KojOVR;wq>P0hk@=9~L4ovA!~+=xg$dV%ILw4;!Vw;;mdDSF4dC(f z_h*rh9kx~T6mUn2d^#^#dWHIlEx@N6RfjgfRwFy(Fo3YOz{vmCR662!<`^$9P)~$_ za(G-^wa3BssLLbE!5n~Y8px_;azN)-SJuQ&;SNb>XJ_1Kec^(~$bR@^1!aMm-9DF= zG3J_TMfQst)?FA(q`t`9Y-sXA1rrNP5G^;T^_&CT zG76zjfZ?<7fS=o55+xj<^kD)p9g`&a=*NK9Rs{5in&S-pne(gMZ=RjeE!g_(x^U|K zsCI#&>&u>>Q4^{a&TCrfi6Gn&J@W5Q7x>57!~io7LYnItr%B?Usrz23f;J|$bKx1Ug4uw1pi2aE?4e zmlE6-;=DPR2FGl&UG;-U9Oj=sw9yfd?CCyztaA#7UH$uwHWL*C#R-5=2ogOgL&Ixw zQb9N+ZZofKzRnyK(plR`x?c2+xHL9hUC8aMY?IFDn-m(da&r3k1<|HhPqZBTP`iD) z${l3!U#}u{@ifpH-sO}~dfbZsLPrcGLnZ7cG4)8gxVXPcnpd~T|upO0B4dB?U3FXS`;Esn}hlf{ng6M_MgSKS>At7?#2YB=t z*%5xjR_#Zw`|;ouX(wyQMx>6<8FetQ+;)j^rwj;D11rIMD?=KJ#Y!MBE6RSAxHA{i3a|yzVjv4B zVP_2g2H?EzKX_pE`#NaMa9kPWfGLlE{Cs(mFg0;6GyPTHYBI>ai?=$3pqNDw!L$N6 z;fR>cq7Sx!Fgc)#9=gURCg=@@!%;SJUm%yUKz;e5h{9!XqY8%-Ak;HeL<6BHBM>}z zZ2hB|Sy(mzU$nKRk`B`<%khn*+a5ay`Ns^Z087p=)08&?9W$U-JHfP9#N@tF?ebz= zeN|GjZ8947BD9}^$RGdX2(1!et+4}rJG2kqJe#5HGrR(_Zm=5wMWAi?MUJ^DT|oWK zcifzC_MOnU3n&RdVYEdw)IbfgLGOjen#k@(HDNvAdmjxl{vGjtnT}^-zo&tdb6FZ^ zPOCO*`U=tsv|V24sWaz6L%oToz)Egu46Iv&nMhgA8+68URXVL)7frtd&J742P zGkV`RhydvPRci1SHKE7vW?RDdO;s~0B_ee!Fao+3K#>By+QV~HEel$(z^lC-q*tu$ z?3TFES|a=bHFqA^#(^wY=lJiN{b1lU? z>fp-R#8ZT5=V}(3fWl0mj$Wu7;PA6e5e!#wV#PeK*gCNGgF9UE7cKN;i$JY);DP*k zAfwu;Go*sEQ{t8)>4~5&9Kzfd$pwuKiB<=(2!Le=2M0@$wQ3A1Zu`%LK$TZ`Vj1Nz z)V17IidLj*=aycss%EK;u7Lq4p|?eF=}*>rdjWkk1!V`omICgbgTwjzhucJTd*{$Q z{jniXMZ20tWfN%ypa)uOUIkI%LljC*SRGiBs zr;hq?|L6V)qDA}nW@~KVF*LDbPVAwNOBjG6iD2Kg0{<9Y@vY*xtnvS&g!CHtkuwu)t+wOJUH3(1g^8+5HwqPtlsm;q#o;p6$jrd-4w?g7f{EI}hQDvFXMOh@{xV~V-t~JwuYqPQ$TgT%?X8wS^^|;?UcsFW5f`5y zAAh@GWPC0(rTus2h-T(pZ?e|*ews(*Dk{^VA&*H&xC^LRW1&;ld_4o^#U;hXrA1LR z)Y_IS(b?HFUlYtSax5}M?h54zl}LwtiQ>CuUcX73YDpX!w!4yqnftEi@T`n@XUsRy zuxn_M_1uZIalBhu*LiBU2G4?7lN8_iCV9<1gb>>igpVot!28n2yVCTXzLyfB8Y!P|629okxI9qZ#W$ypB8gElVCvSnF8x4oE- zd(Jgm8@(7&M=O%#m+S>CEid`SK`j)bn#q!qVq)5XU!pVOdh?|9y>!TJ!L6!xk^|Eq zwIW_V0X}a@{)ZLLlUdH+cNqW8TO84B7cm691fIQN6F7 z{q!mgKNbCv9g)b$oX;c^5XGmKJrp;((^0Nq!SM6ss1RCKNx#52mmwwlg)X&D_ym z!JmIjFYbmTanl8q?HVPwpDQ)behL>ELmpkA)NxAfP5mYF zTTSDhG4*HVmkV6SqI>?EyKg9^jWe|`a#T7b>fMM-Wm9HG@`c-=4-}l==ys%#n@w=9 z+OI%v2meQnUvJi?g{YrQ?_-{+V{1cOSngRp`LcJJ9VSX_y0SwjHhFQ_>)IX9-Hg>* zbJ@t#K|f99l^BF&hgbXHtmAnnhvYk!zdunHXTu;qyo^7Kb?MYiQju7f{3>zpZ<5a z`A^m@c$$J#ev6TQmozg`zke0vL*`8n;(i9p19J-LML^QeGDBl~n- zNQ9UFJJVUZUnieni<$iP!}02-Hba8erqKIiFLOI+QSn9vuQjFH0P4$0|^1$|g9LYFYaNA)kwEJ+>>%iKo zQyL(mojeyH{1=?NAbS1!j$WwrCiTC|PaSjZxpYXc@h^DDtTdzNz3{}8e^uu9t*p+JE@06;!SA`xacHoIbvEk2RUQ&Ej}aZ>-}{N%Z;!EE&j502+{ zN&<-h56EVr8CD`b%mH+IaFq7km8kxA(Qj+1+|j--s(*4xl-I*dv*#FfaM%~ymye${ zj1H<7!#t(LRdg)u2d5jNz0RhxUm}mW_A37?n)vh(2xNb4GsaSjo!#qNZg^7i57rH= zWq96w39IMQAKLHI_Z(a8W{hy^6MF5j(Hjr$Nn`t--YVPLJs#?tMEQXM*~n{bKb9ws zztiB^8;OqnFtgl@p&@YLETlA1YE;-`&_1C%6qX-Q61zFsgAW zzsL9k4!t34_L6vAiGhkeydo>iJU1spt5oDH^v3$}wcjtASp_s>E}cl=S$%4X#H{=0 z6OZ%H&xi+urlvkqfx9CknsT?mMlIbZ@caefZe*mJD~|*%wPP|XMk8l*Uv!aaS4l^H zA6d|>XqWfyIs9@wLB~EOCZ>hi-5Cj86BB}>7mFjFxb%sF$BPKbI$4wd3A=9+9lw7E z@-RP{6bcM@y9o&WoWHL$bt=&TLu$LCHgsPW+jvSD8R^K%4u&+yMpAtE_jmw5k|W^S zOKf+jbdkiPGe}S5v3wlC{wMBU)8UKhz4;672e&v;dY^v0sYbe5k6<^0a6fM6H2Hsn%E#=jeG=lQsk6SaYZIRw1ir^Z zP5))4BgtmWbOQD98)A1!7$xNPs8p(J?-u@{RPhuCJI0nLVed8VCmH~p!gJ2R>^<~k zI}{$Aj5ckZW}@#9!>)Y+XTKcdxj@f$>tCN!5n7311EaIG$Iz$Bot!0uiP7uf&_En# zDn{eo6J0HnEFpHUB)WfXoo#7+!lnU@AVvv6v@wQ-nKfGVe~8 z;1!~D#y&k`Cr(@t06qH2pK42NQ;k15v@J<#l`H({k4NP7aYha+Y&mIw6>K&x=$OMhdW3Q{_H{h74(x#^&96eluBR^<=KW@xg2xp0*W5@*{>* z;FlGul29P3UblEd=ma4&^<*!uz;lw$L{Sf086Kka!C&QGTz`J?dQ5C=mg7|*+%NPK z`Xi;hd?kGSrV8xJ74c@JYf!GZ@pA2phb%L<6M926-Jk*(Dz|3dS8t+?PcF>bq-%xm zG198;jEc#&67I1=jRw`Y9tyk9=9}I)zI0OLjQb&wYnq8^Iw?aMI40B3eXC?a|Jw75 zj85=7!X$Of8BZ>d9Upur+$djm4n5tD+mdLHZOJCDkwh=d(fsNHu+{mr$hDVOm64C0|4ikz7MDEcv0&Zz(bz@q`g=>iE zdUqmuA}iACTJM)X0;gOVfeuIr$8^j)A+FW#K_UaurhGBE?nb@e+F2vv{4Pi1&myT0 zNUe`NFxpW5guHba=bprsp;-gM-D_t~Y)<)ZO#YtVou6?N<@JXz4sWHm8@1E8Pcl<3 zv_H6_lN!7Vrs+vI$WOz)H#556U$)YK-EZ zxy426{`;Kcd51O=iVLELIUW<<6?Iav6w(_%Hh(UlMVW>;T1l0!pP04k8#g$Krkeo9 zZ=!1^WcTjEjX**8;5XdXsSN%*v7`FVt8H9CHWPye`i(uTurj`cD`f%=PRY2vs;1k> z6Z3uMdG_WX_YJN;VS2N1t*h|mD^ztXO7wgnWx{P$6lKKx)2fP6*41*}j)5w!P8wz9 zhOPyVpC=1qZNSWjUHi{aB8CjuYQ;mgx-+Itj4(@l>1P@e8NZtgjAY(@!9>>5PjjWB zsDGcSnvaqDMKZ|>%#bf;@uwDt^*lO49XL@-YwNzHzN<}2{Tiyf0U;ZV0tDmglp8%k z^I^*9L-Ld9YQe#D8K~7_8OnDrIuP_cDQT!uChgi$VbjjZqC1}F1T)M|?>z@neNV=e z>9WAZNMcY(kgJ&+C%kQH{u;4L!&KVRWS6MRbnjLWK4)Bl9K+gB_o9?Fbpt})9d_r$ zz#q#YAJDqMt6jAP^Y>zxxw98JQcsjS`TiurvxeZ*M88Qh7Qzb_A@ z-Nx7OXT5V>i_f{E)A^HN5Gw5dZv7~JUBz!qQGeaJa%9>1w&$;gvq#LpFy6yF&!Dm~ z-)7B|XMz{B&1BF@DchTrVK`aVY6f)lYMYPX=_7|UNn!{(wNTQL(sd^G(6!uZcYJvI zhk3)CWYVX@^Vf&2#=r7AWKc=yVErxTPo!&ZOxIA=2Q}V0ppZGbZ}Cxvtfe&UPPsO3 z!;*71iQ_MYxOwwRm=*%84( zKJD_Ptq{N3>c)&sRaI$?-nyVqxvLH3DIVBC?a=l0con~I3&387Y%CPzqDfu$V zOj5$b+&Xp#3MX{cucNJ>+iPC##czg5E8W+7=SRDacB77-9wU4BPV{XB46MdKQP`1F z^5pBt98CXJc?l^~gvcJ2MEdzQYwEk@-MO^|s^Qc{-4lI%>UmXl%UK?!DQV9jpY*Vx z?2uiqT3Ze$&06Dxy>#c~d>J0EGxXj?cMV%JJY?Khnag8|YBT6*QfS`nrOMG~ZHOHC zb>90p(L5U&L?PvB_Ij&7Z@MTm-HBPNP^Q!&_kKv*y#=F`Q|Vz-fj?XlkLB=ta3RC3 zXX4#-v2KHIn{VGkH>rgDcyX*ni|<*_z(;l?^INv*)gm>f*G{6}=^97KPQ03zkIkFs z&ST@2FAQZ$c&GOTxeJW4XZIi*RE>L1AL;+a0q4l5>Ly;U7y50E7EM3-)roIq3+~jm z-+HnYxpFE?n)TzcuT$QleHKf$!k+!5X4-GjiG|M7n(e1J_RIa+pfK{*_7gwey#MI6 zf0%uC1?D2#wtAW(zw|mpZG2~u5S2d(jx7l})48Qll~jI`vn6r+orj~WoK3Qx z3^TarKm0w8<>(cq^naB1ok2}?U%yxoEEGk$7!j$`rASAlNC&9`A=0HkNbd+JA_7X4 zt{?;mz4wUpF1;tx2`zL;AaJ)n|4(=3&b)W#edm6-Unt2r`|PsT+UvL0I{Wt!v45E| zqt0t`v3f?E)@a7&y>x>8er0_k;j0#zy34=b%7`DMA=b|U(jh^Xg`L%)^iFC+b0QZ{ z2p>4)!vCXFTESrBl6jFmkV6d*87QrtWpFvg^}JP9`PYtdv=$fKKmGVn3jQ_a{H6j& z;`7UYx4iCB^pz9As-~6OkY%=aABVT8&+mr?b1M9mgs(!7l?&@)XETr$BZJG~ZA=h2 z`=LisjQaDx-RIDVd606>^PtQ-oGI2{!Q0%0itm(yk?TKGN#wrm*{L!RzH)bBQ7YcY zl&vf);zR>V7@+I+IEwkJA+oIBw@@a8k2D&4{>o0GN@W+$rA7xo&Hr^PgM6OJy$^7| z}g^Xxq_-_br6 z`d0Jr+}}A+;ST=wI_d!ht675Poqz3@eN^^{V^PnLlCqP_u=%Z`Ir+bIb3vWI>;-%{ zRboE8|7bb?uRJ}Yep8Jd42k~>WB$jy{QpM4@Lz4})qE@oC-e{9B+=ta@ ztaG)8Q7;plj<3DFae3u%zOFZzSWx(2+$%Vt zq@da0w>0sxDj!wVR0SWfvOR=83soJ=&R!id>Qz7Bsaz&6A6f!e-P1c)uSt9 z8e`d9&}X-mRn#<8)SLu`M}#xXdZobQLYZ?f%TC=35fIbCaAoS2@e034G#b{0qD>HP zJbwm27u+4xYQGT4`IMfw__4+79BJvioE_czY$(PEd{0SKifM0F8K*3IzHi&|=ygT^ zFh-8kNZlk~dUndnv6{aV90PZ6x*EktS8@rvI^a8Dk^(BXYD#l(7+8+DDsp| zdFWTgO|{KzBU@$qDZ|Y?{DMe|`rilq(iBjkrL;CTHxIK}{PbLxms7yoOHo5jtzhUa zxczGeDObSz@NlN|&wameC0`@PDF!;(5a63o&fpKl50n`a(&OpiI(3xCTml-R+)}DB z53;ZS@h;G$TultgBEeO0C0XDub;fUQf1;|}j8EFw+vAu&s$SJm4(|egsThFEiO%`g z4bWhWpo1y~Z_ZRzyH`u9M61RgLP;sL6L|6ZW>3ByS$y!M39Oil{WWrl=so}CcxfN}aBdMH`U?0|H`8;2e zxQd~lIz!9iDyU^ z6f#Zu>PG!U2>z;H{h`yCU8Njpp}sM{yAc%XsHYb{U;rzbo(s)mZW1TwEkHF-8CMpRRf5 z+Sy9WooXkeofgV*D)Y~Txr-M*%Q!V8Hn!i$f)y_lys%i}d%L}S$)3}Z)%z#=KZ7yV zg{$WnQkIX}E-cKv%+C`&M=-aXja*!tM{G@_l5~U&;KKwo`rKMD>8ZXU2P-QZdn;?j zUw!|oi1S`JUyt(L7l$IcD!iwcE7xT_FgJ^aF=gdt1fRZ*0*4dB$2i5O@60CWBbI6V zs*&T^^IZ}nXwa)q8KMFAINOR!UgD|IwHt*;DDr2@T0FLDXY$$hT4}u%W}J0G*$7g} zG~S&vUjw{XnDz@j9AI~h94Q!jm0w&WljlYqw6^M2Cv!FcF;T4C;^TwXpJl~BB|<3^ z`H;OmB?f#(>N%iGMtf(|a_=!Za314^k?cJ~M3>w60M<+&a&NxzxC42(z%W0S_7H34 z;BU#2Tk|12ypM_D4T16oJ-tlRf(EjQq_800?~pGrzYuZVRPUO&TYdoo0jv^{`%c+M z3w1|z#N?u&Yc4WMPj8jVT`}Y&{iVIAU+>T-S<5{X#(uAg2OM8el$&1>X!9A2TF&&W ztWDKDd@Dq~t0)GZ?k{dwDMw7FhUI#Zm=MSR48wJiTjJq*b36t8X{xpmE31{p;0!oQ zMMOlHIFFzBg*eY3)9HXQ2oaC4$k2BA-y+4X6t=@K;CN@ct z`kuLW@`tRPqQh}b=_a$`_Nh9f4i4N@-PF)-*+Gg(6w{-zFLTRxttUR2{poTpo>WoG z#Y(Mf#BM&W=>8|Lj9W<0>>^lSfl;By^2Zl9ll08Le__)J0s6qT3zKP@)-hJJwM=O5BE;J(M-lvZx{U45x%2kfQ2d6L@2Rcov{uz%)qrU3z_mpz(@ zUm6&hj`>o@b)x{oTR*q9PEwQeL>=n5Q*NIoOa z;**?S5w{0MS?(6$%kgox9-r&^g&$6s`G^D_k4JGuU|G-g8}HzKC(?2 zkp0srsUdk;IU?2ya&w8CNo>YW1Q)Mow`Lc3(!uU$LRMGN;PCVKYSX}|g9KGr>{t#iX6R8L+#{M&i<;Tcyy}D~iuv57XK#%MyyVWjz9kt^#EaCS{ zG%US3_{&Eh*o(JJ;;(DG47%dNb^Pw=op-P4H5K^&EtP}Id{MF+1)!VZzsQ|5%B_PFl)SH>91Ni#7fj{ z?_f96;MlYM4KOOxFB6`{PVV+iLQp2WWe2;Yf8~>ey#J2hwimyla|2*Sad|;930X|vOZ$snCy?JkCPPz9m!T<|g zBjcKtl$QJ#;d__kZ#(sIzF*SxQ3yuqRnsbBQb%2882J5|%<6jqO#aQsGEw;V?ls|UMz?-PBvOk1F_^#jk zIpeXwlb!>4kS)(_r0e76;r?!6zm@KNKk%4UiALn$!?RWM!1Q5I6e-e`(UbNcHgl*c97x;% znihh28qx)9E3yIGoXVe?a7k8{n$!?srb4#T{!F<(Q}OO@TDAtfhv74I zx6kd{QV{XqZKs*uGBtP~h=K2IIg`@UvUwyI@1G;#k+#+V@)xVn@L*xc9u` zM}dK&&zEdrnbdYTB z&bLcVqMn`9G-}9$b_fP<7h3CwvohdDHb9C4+je{KE;K>gtT3l1PJy^HN`s3BU%?>j z-};W_wuF&CdoH7Cq9qjgv*y$Qb!@PsA$omuWhD}6j#~<-%#H}AuIh+|j$^|8rYn`$!HKfl9er3>5-imJC0Y-ZVxm8o2QX(dFJmO18{e@U^9}9SXxx$*m z0VS4eooVLI4LV*~(|b!iEahIe{dVxBNtKr^k*Z*lx^rVSTM!@VJ?`6BZtsn*4(A#? ziP(!tVfRX)nE~sK)$zR7RPKZ3DywhlW`^Z8>S1mXEhVY_abwod&tmR*?==b+H+14v4GUam|H#;N{jU3dgREjEn|UoLOW8%)t@;bsmg$$@q}N z%+9&e!|2aW6|n+4LkzLH>1eVN5n0{TH4#op0W|^NevpuL#wR3#1=Fd!rd@{kUSWb0 zBqFkt)p8kiaMG>29C{HV^n1ScT&IaM8JEtjm)1MVlRWx`eIdUcm&BT;3p6Ce-zZ!Lh2b6;?#H?IzL z;s)1bg=2(&UuAX#A0Fk&i|sIJXDi3_9gR&Wb$pXI~) zYE@m3=y~0&wi#VC$bZa~7;=+D-$~Zi${n>+w;sm#F=lwrO_HUE zq)YmD2mAoe#klj-W2^bNk=ruXuZwd5Q(hMGpBvI*-uRJaM`IDk>CACVuwSKtuxz?vhuQsCUhvTLA9Y_smOK5?nYtpTtv1z>kK>gMOh>zhtLPE5mgFD+f* zVjo#EuA#pu$;HE0kLx$y+iUk_7;zFz;>zbbUAt6uE^CYBleb^D%Lv2~@04xtni6HU z0vpk&^I^8LAQj*Z5$EY!^D$qqfOQ?1F$1U^nFnWzOr zslH8CBm0Bv^@!|Nq{M*C9Us`S#a7#*l{ty5Z>t@Zc>>q1lj}|XugrOE=w6Z~G=E6% z7+%nD`X&QypvB*(L$RBm-_2C-aflP=Ydjt5Pm?H<^s}%NO!3&7nUmVg)raoU1B3e0 zG}3vqh#VlVC;69UEq`J5O%EB!+CS-C)ML)|vMe>SOU{q%N&>5uF;xc$C$p#2v=IFc=}7l$@YDy0*AEx~ z>O2y=dy!okxo%`;KfVY);Ng6V;rgX{z~e3)2?tvGUPzS|CY1H{*NFfO%YF9V>du8@ zhk(I`tKK-pkkXtg>p*W#M^5diuk#2q%!`{1Wh$5L1w_*%jkEs^8c;xye(w2ak1wsa zMCCOe=n`E1*n@wA3}urJ>;w8q5_Gp1M0%NWVr>u(VAJ~(y!hdKnOepT$ayZ}JCv0h z1cNk3^gP_%fz1Tp;zPd9XA~9nQ@K;~EVE@_eJRJs=ge8Mhdc+Z-zVnb zx}b$NnKpOor4xGmA~D~MM;qQXqBZW`mG%4Tu#Ow3ETbxAP(O$)w3!y4 z0)Yo`S+kth)rP95s;a11;mFaDTRK-**&dBZYpHD(_ATdFyRuyW4z7}y1OYcNr6N`7 zjt)|Y|1Dxy_w!@ru=y5$(v-3BbaG*ues&Lt?a02T)D7;RvUPMQbap7Dm3t*T<>e@s z%Eqig1wwuR6qE{-3dB567Enpd<4-9>^jNQNtW|gz6{IDQ+be}t7S0Z}bAV`H(5k%F zRnCY}^HQVGSNeaWTe72>ga|*-FCwnLMFjr?l`Q|4MKM|0bwu)stk-F!_kXD15*`*I zi*l;;^L&E-xk*Ju1<80R0(PhG6vJ>0{I2a;HAan%3Dfb`xyIX}m6 zNoiA_n;am~phX+0xEJ9Yn4T!ne_}StQU(G;KVk6S3;)f2iMD~{`IldvQ5o#LXzOU3^QC`vU zo&8^*Su_cp@`a({;o-Ge9S8Xzr0{SEU;efd{T7Z2CGn2EH}pp%)1JK8v}{ zidpItV865lC$a)~FQ8*XkU$2B(-jS;gt*`f|AFxh;huc^( ze9RHq!>kQ&Zf*vQS_{z)4-bW^kBE+qkBz#!yW`x&DVr|(I?~^J`mXsEKRACqRnkAd zy!??DJA>d2dRq*%s;CZ+FAq4BXL1k%{^}^;Dt7Nx&!-Fi@IkU+cjeLb>r9%S*x8fc zfim-6_z6BpvWR>B0wxMte;0pX;iT!ebTBSGUA8pF=qw(%tk$SugZ9d=!ykyA6An+X zzJWT({>n!THy#$%2Q2Snl0rx%MA8e3&wZ4-WK8`xkHZ(vPH%)#nvZmT9{0m`CxNOi#j7CGxI})I1z;2erV#mzEo-b25;6R&K0($PcVMI7YR%1Du4nH7l)Emi}k;M+f8h=2ZRk=JbWnmiNYK5@D#u6QuX@TN@_LK)=1sT{Fgdpu^_M4rogZg?R4>CL%Qk8}ee2U33oNyb5DNcP5QBzWF?@YTT7K&>bd?BRVV3{AV zv5~8+;fQ~+vUMM=xr^gWl|HQco11P;V4?U~Ajqft-;$C%(K{BAiYM6WaW8MMrqpsK z{LUzX=PXYkas7TpMFb5`y6f2n``Ph6JVn%(;4AT>&aw+yjb=Wd7F$b|u6zl~Yp>IC zdo7A0ON|PfkZhESc55cnQceDYtX3oxok$74L+1gptxG;BeyHydw4V|d-CYqka(#s- zRoH(y19sZ8<$O3Hy5^2YRrAfyNU8goG{C}ag%}cB%q?SsSlJ$QA1z1S-Al9TiKM@R z+j)A$2Ia9($Pjrr%#+bots1i*3z-%0ZO83K?zp%B@mX3~>cJ|A`wGYOrs{k3e1M3R zeQHSOq=Ks@Jzk0^Mjr1)3-IKtVr_YjTI%j} zSm2|h)B8AeB)dh&BFZ%^1xtQsUz{*ojgF5^qY5%2v3@e>ml$Wxt~_+ahcrTrc8i+2 zwvF;~wmnII?$guX-L$Yg?F+(XlaH*?ZiH(Pl`B6dh>CeGe73!_A;?oU`>FY<34_In z|NI7hxl{el7>cuvS%7&bDh2yRn}2rrLisftvF9HsGs|PiGXcDmFKEwUE1%pbbJ2P} z^mcEBWYHv2(RG|j#-o;|qM`}}n!Br8J37wy$EN)XaCL|xaOl0Oq?8_%@DqK0whv36 zt;aIr%p&+|?s@joL10v*d?G2Eo97J+WiCJ!Jx{j}Z8DCB`$T7ics&c?uB^&+OXma* z(?it8$+&a$;@N(w^# z+lGjF=kMO^2;SFX?K5Lzza}PZ9(67y`iW{x?_J6(65ER&hk2hZ(HTSJL$SH9ZS z%!0X=CHAHUyU~ZmNb5=?z@Yg1O+8jwa{O3=SM=zV9?;?X_-0r|_qcz6APEE=2~RAI zoL`yCLVa=ZSo(>GLe}7Iwe+95w(oTrMI7%Wi!IKx_Lx+`AHYo>JI}q{qtVyZ3#gdO zQa7%yxx;TW=Fo9WRuA}cr@}G{DkmLu*wt|u9vNk|)iVm$tyKYUi;j4R!jkX>12S?{`+{eAJBI$;i9(*9|w5z9BYw$H38%sSM?#~ctN&I??aeY6qv()1C z<`Y27xm1k|pI(3Sl;p%u_?};(&%)>eyQYb2+tIAYJV@d-xwr8|@EM-YoJ$4n zj&ODyzIa={7{>J2Z#yJfra}g=2p_G$0-6?s+oBqW*=RYMb}LvuIH0vnTpH}+3Z5qW zT?pH2*Zh>B=iRWDQCwUmQzUL$={-%8sBEw#)Nc^4J;}29q|F?2ya3DitQqW9G1TXo zEgOnKSF#34n;d;6Gy|jRr`O<*+Nh0!TBIWNS3APr>ZDCsdTel&!f!LT)@hDwrajJn z`Fh~H{9BzI-)meD$K&J5YE+5g7kgdr%kiXc`2g%TFI&TD20|}8FEskec=qOM??OAc z5bYngYFmXw#N37H;}>+oju+UY;MqCi>8PD7NMDMS?+Qw){&3?(wM!OXF_HYo0cmuF zztT>2Pm|QjshCrk*++UYJkmh=wDpmu*z1$djnBK6@}ms8!cv8WMSG6K*#@*#-23Qp z!(;mJ-8-`?7Ut&ftjOcBQ}|I4+%Oef!^b-j{QREt{c-x)HlA^SFSXY5yvg6{MAc(+ z6}BX6xZY98`pm<>+&Eoy==m_97XYf+kUGEAdUzOhvn5sBCvL+7&x9_u36snPq;egt ztu<*`Gdp^ux0STQWh48CVH2S`5GUnlKOdn7;@Ph;w+^*MB=VYaOFmQC6$wV09T?lQ zeH@66jI5N2{QR9WmJJwhWfNbhs%lSBqp!^IaaJKL0Mny-;pPuZJO9nX(c)rni{*X# zJ)41)FM;Vjl&*~$fk%#>vquOLwH$qBdArvi{IHqCw7f?De1p@DWBl-iH|{3xIPiw3 zoA)35dUiK%V+gz6nn%@y?5T$|i3ZFZPq?OaKTuBoA~V<|F0X3egCd{{&=ub*8hX!n z-|#PE!4%lpLD}!CDr@Yp=GI7+mJhGh1eQqX`5T?>SGb}z3iUzt^Sq#*z{tDi{hOVZ zubGk!pFaKAkJoSAKStv8pa1UvzLqZ}L5?lyS1PeF0oTnRB~o7K&39>4+nc z1~Mr2ZZR)CJ~GCf6c=autRy{8qql>@jPkm3bvx`-1ycK0x=3ik>|w z9BmFF)yZnR({JaQBv814KuIR-c;)ZaQVZnSEH;!<0nluH5%C`iy;@saD^S+6UhtD% zhg}m@X`_j>Bhdw?{qx2L^3@z59HUaM)>E--#w8?n+hEl-)oJAn3-x0szb6vHyjRkp z_Tzzzn8+Cs!CWm19Sa)+g8~P+lHTSdbF?Ya^m|p&VOwC+gV0WhXh8S(F5JSdt5W1= zbOp6WnlzWq;@}61H(ZQbDFHBJyrt0o-dXFXt$jQ|fKA_;hnxM=CnhH`yH=5dFCV|& zcs8K3@;0SFAED~$nM?UZL4lhl;?y?N_0lV!irD90DUrBax5oaC!d{)rEi7dj$Y za_`+A9U1L%W^0dTEFOFXo_IPi#(X45M42PY>dqsx}$T zJBrfkJu}1>gq~>mrB>JW1a0dzAPmohSa=9Fo~VbgXJ^5+*V)9*m8JxW_Iu`w7pKWE z^b|Ahfr>uw2zmO5##e*Y0Q8spZq-a%J$tA6g3{@5yOP8)l}^) zC69qUG546^*}#kh?u~R$c6JH)t7QjV(bKR-b;0GH@cO1n9&|v!94>ZX{VYH8LC)l2)U1UVmEKwJ&`gtNg3u<4d6hiZ^WI&Sz;eT?xb2Tg zI}(%utAq-4S3>A}lUXpKqHC+_&PT`iG2BaGy_8 zdU#?)H=yLSI52P!-Ewg&R&yNsIPja_auH)uQvj&&0Vp4p7|VyMEFZOe?WUITDe-*R zIH19GpkF`McI|s!mTRiDtF+gm)~7X+k(c+z6SAl^5I1Zqr#PaVRld^J{tU7UwLYO}{70ZGhEOZL-?hm^?b$?}rtd)H-xkFODas`FaF4wk*t7${K>I ze($a973e#6<(Ti|m`9NzzG&q;d5`gf?=&}}9Yv-e@ky@>9zPHf@c>Rg%R92fYP#EM zyq^MlN^FqppQ2ksTbV;1F%ftO;@rPAQYA}W%GXs?+HbVHH*2Y6aE4VU_2pE{cquhm z9e&)y9?xwao$rHpFSui1dWRd+uG{=ih(9}ksoRwJ0ARFxMHM7fc*S;9Emb4 zZ}-g@EW5M>SfIWKzP)Icy)QdioZ)wBsHc#jlmOv6-yBB9@)_!b0;OXex@5GuBNHdZqgQ{(g{ZUotF3 z!LFHJ$)bWHN0tgb(WQpmq9(49eo~n3{Crx|0Po}Nx!vY=5Jn-I0xI7G~*Jt`31Y-MYZve_kbe%5Y(4g&>>4Uw6RSy_-U;-|pWpP%h?VNG|_ zfGoJE2{@D*N}t==`#)`2LZ1~abfQE#&I4BZq)rY^>Kt&PJ5-B^jtPgV?ghOJ$Jza^ zc6TT2o1o5NtbLD7hI5r+fr0;#@2*%=zQ&zEtAl*zq^FC+US2NqBVD3s|GlY)#qMf9 ztV)ekJ>nZq*JK*2osZq;Pap&v*yP#61el1~>17S+aANc7 zgUBv}*+#xlO0dcIGe|cedOQlses(OAwnZ|C39>(w5N54yY+Pu~i}h`y*P_xgGMbtg z%!<4cqL^jtV26U4E(@mLeSa(FL1b^gfF<26s#^dA4Ty|D3Lh_9n21`^y`x}CmgK0- z%31&h<3qj*wu7CG4b*m}#H7v)4!W$Vxo3wh0K-oQYDp#7#y_Q-IoV6N?LTvM9o`h3 z6?Z-~>A1kIk=Af-Kv5$lDj?v}S~7>R#TZ5AfQ8sD&1x!mLnfbp1~pb(`gkWstQ7<* z=SP&iTMCTF1F$FPrd*rRXD7vQLiNNKGY4BoF~5!X)36p0RDqA$0iOe~<57zkKebo@ z%MN|_lOhyM%HP+vL*ERtD8aljO9M^&4X0a{tDl&WXKh*GSEXWW1NBZvA?J7-)@zr4 zcRQj;*TUcP|9O)(Hx2Atn-t$L%t~eJC#O-Vv}OmS9s(8AfOePqfY)a zBH5RJ3hM2u=cgDqhI!#PR7~qu?`wv|Z=kfx?3GACt@7oY<>1AX5e;clRBTzuQefsV zEs;E>(Vx4HKs-<4pX1-V+2{m5gOy(sGyBc#kbv!-Ff|l0(dW>w7l}wOtxH3ZXXRu7 zUMGPv*iQaeLdk%+?_EwiVd(@piHv@KF;{D{`E{ZGx|b1FQox5_Lq61OjPFk}CBKF4 zPp$6%W$NR&jRjf9i?+)0Pjvs^OylYK;g(@#7ypL^{>-Pb&WN#ZQ5wN7 zKTjw$hOJG=MvM;z)33R|f9s};eFD|}z#C&41dUOfQx*5Q3j6$1j9qeo`ZV#;>AOVQ zp_|~`6or2x|b#~c!{V8QC+%&A4LtPv8xZuJHIiXs**ZCI9Y%X zAP|UE9-{Hr>vLHhgN#IVo6?LQqoS*}W+U%je5-D=H`rB=l?hO!xUkBm8+7rmgLAY@ zgEPKym08QQ-h_+-ecIUY@$SW6&5YDvgF@z-RNgQS?)#Fo5LT&)W<#%O{pg_`$YTG2 z|3^#qsvI%LkKx-jPsp!vJjo>@RrD=eAa`)`e@wy9dVmmklBR`7$+XU}}lW zhqxfbqgI?LL!p>x)x+0yJz)Pz5P3w~{D5Vd1f38eV4sG=K|BT0$fVB8n;AyrwikU( z>sUqZ`_2NiH^?m%ye9W4>d(uU|A)z9`u0D2@gBvwI;FetTk6g~ PTCS|1Azvi-GWdT1HBeo* literal 0 HcmV?d00001 diff --git a/build.gradle.kts b/build.gradle.kts index 7b15349..d626eec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,19 +2,19 @@ buildscript { val objectBoxVersion by extra("3.5.1") - val hiltVersion by extra("2.44") + val hiltVersion by extra("2.47") val accompanistVersion by extra("0.31.2-alpha") dependencies { classpath("io.objectbox:objectbox-gradle-plugin:$objectBoxVersion") classpath("com.google.gms:google-services:4.3.15") - classpath("com.google.firebase:firebase-crashlytics-gradle:2.9.6") + classpath("com.google.firebase:firebase-crashlytics-gradle:2.9.7") } } plugins { id("com.android.application") version "8.2.0-alpha08" apply false - id("org.jetbrains.kotlin.android") version "1.8.21" apply false + id("org.jetbrains.kotlin.android") version "1.8.22" apply false id("com.google.dagger.hilt.android") version "2.44" apply false - kotlin("plugin.serialization") version "1.8.21" apply false + kotlin("plugin.serialization") version "1.8.22" apply false }