refactor: objectbox to room

Migrated app from using ObjectBox library for db to Room as ObjectBox is not FLOSS compliant.

Migrated app to using version catalog for managing dependancies.

Added floss build flavor for F-Droid and general build flavor for all other builds. Floss build excludes google analytics and crashlytics.

Other performance improvements and refactors.
This commit is contained in:
Zane Schepke 2023-09-15 07:24:19 -04:00
parent 991d1224ab
commit 3c5aff31aa
43 changed files with 504 additions and 611 deletions

View File

@ -1,14 +1,9 @@
val rExtra = rootProject.extra
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id("com.google.dagger.hilt.android")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.hilt.android)
id("org.jetbrains.kotlin.plugin.serialization")
id("io.objectbox")
alias(libs.plugins.ksp)
}
android {
@ -16,8 +11,8 @@ android {
compileSdk = 34
val versionMajor = 2
val versionMinor = 4
val versionPatch = 5
val versionMinor = 5
val versionPatch = 0
val versionBuild = 0
defaultConfig {
@ -27,6 +22,10 @@ android {
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
multiDexEnabled = true
resourceConfigurations.addAll(listOf("en"))
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
@ -36,13 +35,35 @@ android {
buildTypes {
release {
isDebuggable = false
isMinifyEnabled = false
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
//for release testing
signingConfig = signingConfigs.getByName("debug")
}
debug {
isDebuggable = true
}
}
flavorDimensions.add("type")
productFlavors {
create("floss") {
dimension = "type"
applicationIdSuffix = ".floss"
}
create("general") {
dimension = "type"
if (BuildHelper.isReleaseBuild(gradle) && BuildHelper.isGeneralFlavor(gradle))
{
apply(plugin = "com.google.gms.google-services")
apply(plugin = "com.google.firebase.crashlytics")
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
@ -52,9 +73,11 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.8"
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
}
packaging {
resources {
@ -63,68 +86,69 @@ android {
}
}
val generalImplementation by configurations
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.appcompat)
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
//test
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.manifest)
//wireguard tunnel
implementation("com.wireguard.android:tunnel:1.0.20230706")
//wg
implementation(libs.tunnel)
//logging
implementation("com.jakewharton.timber:timber:5.0.1")
implementation(libs.timber)
// compose navigation
implementation("androidx.navigation:navigation-compose:2.7.2")
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.hilt.navigation.compose)
// hilt
implementation("com.google.dagger:hilt-android:${rExtra.get("hiltVersion")}")
kapt("com.google.dagger:hilt-android-compiler:${rExtra.get("hiltVersion")}")
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
//accompanist
implementation("com.google.accompanist:accompanist-systemuicontroller:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-permissions:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-flowlayout:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-navigation-animation:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-drawablepainter:${rExtra.get("accompanistVersion")}")
implementation(libs.accompanist.systemuicontroller)
implementation(libs.accompanist.permissions)
implementation(libs.accompanist.flowlayout)
implementation(libs.accompanist.navigation.animation)
implementation(libs.accompanist.drawablepainter)
//room
implementation(libs.androidx.room.runtime)
annotationProcessor(libs.androidx.room.compiler)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
//db
implementation("io.objectbox:objectbox-kotlin:${rExtra.get("objectBoxVersion")}")
//lifecycle
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
implementation(libs.lifecycle.runtime.compose)
//icons
implementation("androidx.compose.material:material-icons-extended:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation(libs.material.icons.extended)
//serialization
implementation(libs.kotlinx.serialization.json)
//firebase crashlytics
implementation(platform("com.google.firebase:firebase-bom:32.0.0"))
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-analytics-ktx")
generalImplementation(platform(libs.firebase.bom))
generalImplementation(libs.google.firebase.crashlytics.ktx)
generalImplementation(libs.google.firebase.analytics.ktx)
//barcode scanning
implementation("com.google.android.gms:play-services-code-scanner:16.1.0")
}
kapt {
correctErrorTypes = true
implementation(libs.play.services.code.scanner)
}

View File

@ -1,99 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:2692736974585027589",
"lastPropertyId": "15:5057486545428188436",
"name": "TunnelConfig",
"properties": [
{
"id": "1:1985347930017457084",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "12:2409068226744965585",
"name": "name",
"indexId": "1:4811206443952699137",
"type": 9,
"flags": 34848
},
{
"id": "13:8987443291286312275",
"name": "wgQuick",
"type": 9
}
],
"relations": []
},
{
"id": "2:8887605597748372702",
"lastPropertyId": "9:4468844863383145378",
"name": "Settings",
"properties": [
{
"id": "1:7485739868216068651",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5814013113141456749",
"name": "isAutoTunnelEnabled",
"type": 1
},
{
"id": "4:5645665441196906014",
"name": "trustedNetworkSSIDs",
"type": 30
},
{
"id": "5:4989886999117763881",
"name": "isTunnelOnMobileDataEnabled",
"type": 1
},
{
"id": "6:3370284381040192129",
"name": "defaultTunnel",
"type": 9
},
{
"id": "9:4468844863383145378",
"name": "isAlwaysOnVpnEnabled",
"type": 1
}
],
"relations": []
}
],
"lastEntityId": "2:8887605597748372702",
"lastIndexId": "1:4811206443952699137",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [
1763475292291320186,
6483820955437198310,
8323071516033820771,
5904440563612311217,
1408037976996390989,
7737847485212546994,
8215616901775229364,
8021610768066328637,
6174306582797008721,
2175939938544485767,
7555225587864607050,
969146862000617878,
5057486545428188436,
2814640993034665120,
4981008812459251156
],
"retiredRelationUids": [],
"version": 1
}

View File

@ -1,94 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:2692736974585027589",
"lastPropertyId": "15:5057486545428188436",
"name": "TunnelConfig",
"properties": [
{
"id": "1:1985347930017457084",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "12:2409068226744965585",
"name": "name",
"indexId": "1:4811206443952699137",
"type": 9,
"flags": 34848
},
{
"id": "13:8987443291286312275",
"name": "wgQuick",
"type": 9
}
],
"relations": []
},
{
"id": "2:8887605597748372702",
"lastPropertyId": "8:4981008812459251156",
"name": "Settings",
"properties": [
{
"id": "1:7485739868216068651",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5814013113141456749",
"name": "isAutoTunnelEnabled",
"type": 1
},
{
"id": "4:5645665441196906014",
"name": "trustedNetworkSSIDs",
"type": 30
},
{
"id": "5:4989886999117763881",
"name": "isTunnelOnMobileDataEnabled",
"type": 1
},
{
"id": "6:3370284381040192129",
"name": "defaultTunnel",
"type": 9
}
],
"relations": []
}
],
"lastEntityId": "2:8887605597748372702",
"lastIndexId": "1:4811206443952699137",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [
1763475292291320186,
6483820955437198310,
8323071516033820771,
5904440563612311217,
1408037976996390989,
7737847485212546994,
8215616901775229364,
8021610768066328637,
6174306582797008721,
2175939938544485767,
7555225587864607050,
969146862000617878,
5057486545428188436,
2814640993034665120,
4981008812459251156
],
"retiredRelationUids": [],
"version": 1
}

View File

@ -114,8 +114,5 @@
<meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/>
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="true" />
</application>
</manifest>

View File

@ -1,6 +1,11 @@
package com.zaneschepke.wireguardautotunnel
object Constants {
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L;
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L;
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L
const val SNACKBAR_DELAY = 3000L
const val TOGGLE_TUNNEL_DELAY = 1000L
const val FADE_IN_ANIMATION_DURATION = 1000
const val SLIDE_IN_ANIMATION_DURATION = 500
const val SLIDE_IN_TRANSITION_OFFSET = 1000
}

View File

@ -3,32 +3,17 @@ package com.zaneschepke.wireguardautotunnel
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.mlkit.common.MlKit
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp
class WireGuardAutoTunnel : Application() {
@Inject
lateinit var settingsRepo : Repository<Settings>
override fun onCreate() {
super.onCreate()
if(BuildConfig.DEBUG) {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false);
Timber.plant(Timber.DebugTree())
}
try {
MlKit.initialize(this)
} catch (e : Exception) {
Timber.e(e.message)
}
settingsRepo.init()
}
companion object {

View File

@ -1,40 +0,0 @@
package com.zaneschepke.wireguardautotunnel.module
import android.content.Context
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.MyObjectBox
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.objectbox.Box
import io.objectbox.BoxStore
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class BoxModule {
@Provides
@Singleton
fun provideBoxStore(@ApplicationContext context : Context) : BoxStore {
return MyObjectBox.builder()
.androidContext(context.applicationContext)
.build()
}
@Provides
@Singleton
fun provideBoxForSettings(store : BoxStore) : Box<Settings> {
return store.boxFor(Settings::class.java)
}
@Provides
@Singleton
fun provideBoxForTunnels(store : BoxStore) : Box<TunnelConfig> {
return store.boxFor(TunnelConfig::class.java)
}
}

View File

@ -0,0 +1,25 @@
package com.zaneschepke.wireguardautotunnel.module
import android.content.Context
import androidx.room.Room
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class DatabaseModule {
@Provides
@Singleton
fun provideDatabase(@ApplicationContext context : Context) : AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java, context.getString(R.string.db_name)
).build()
}
}

View File

@ -1,25 +1,27 @@
package com.zaneschepke.wireguardautotunnel.module
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsBox
import com.zaneschepke.wireguardautotunnel.repository.TunnelBox
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.Binds
import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
class RepositoryModule {
@Binds
@Singleton
abstract fun provideSettingsRepository(settingsBox: SettingsBox) : Repository<Settings>
@Provides
fun provideSettingsRepository(appDatabase: AppDatabase) : SettingsDoa {
return appDatabase.settingDao()
}
@Binds
@Singleton
abstract fun provideTunnelRepository(tunnelBox: TunnelBox) : Repository<TunnelConfig>
@Provides
fun provideTunnelConfigRepository(appDatabase: AppDatabase) : TunnelConfigDao {
return appDatabase.tunnelConfigDoa()
}
}

View File

@ -3,9 +3,8 @@ package com.zaneschepke.wireguardautotunnel.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -17,7 +16,7 @@ import javax.inject.Inject
class BootReceiver : BroadcastReceiver() {
@Inject
lateinit var settingsRepo : Repository<Settings>
lateinit var settingsRepo : SettingsDoa
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {

View File

@ -3,9 +3,9 @@ package com.zaneschepke.wireguardautotunnel.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -18,16 +18,16 @@ import javax.inject.Inject
class NotificationActionReceiver : BroadcastReceiver() {
@Inject
lateinit var settingsRepo : Repository<Settings>
lateinit var settingsRepo : SettingsDoa
override fun onReceive(context: Context, intent: Intent?) {
CoroutineScope(Dispatchers.IO).launch {
try {
val settings = settingsRepo.getAll()
if (!settings.isNullOrEmpty()) {
if (settings.isNotEmpty()) {
val setting = settings.first()
if (setting.defaultTunnel != null) {
ServiceManager.stopVpnService(context)
delay(1000)
delay(Constants.TOGGLE_TUNNEL_DELAY)
ServiceManager.startVpnService(context, setting.defaultTunnel.toString())
}
}

View File

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

View File

@ -0,0 +1,15 @@
package com.zaneschepke.wireguardautotunnel.repository
import androidx.room.TypeConverter
class DatabaseListConverters {
@TypeConverter
fun listToString(value: MutableList<String>): String {
return value.joinToString()
}
@TypeConverter
fun <T> stringToList(value: String): MutableList<String> {
if(value.isEmpty()) return mutableListOf()
return value.split(",").toMutableList()
}
}

View File

@ -1,16 +0,0 @@
package com.zaneschepke.wireguardautotunnel.repository
import kotlinx.coroutines.flow.Flow
interface Repository<T> {
suspend fun save(t : T)
suspend fun saveAll(t : List<T>)
suspend fun getById(id : Long) : T?
suspend fun getAll() : List<T>?
suspend fun delete(t : T) : Boolean?
suspend fun count() : Long?
val itemFlow : Flow<MutableList<T>>
fun init()
}

View File

@ -1,63 +0,0 @@
package com.zaneschepke.wireguardautotunnel.repository
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.kotlin.awaitCallInTx
import io.objectbox.kotlin.toFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import javax.inject.Inject
class SettingsBox @Inject constructor(private val box : Box<Settings>, private val boxStore : BoxStore) : Repository<Settings> {
@OptIn(ExperimentalCoroutinesApi::class)
override val itemFlow = box.query().build().subscribe().toFlow()
override fun init() {
CoroutineScope(Dispatchers.IO).launch {
if(getAll().isNullOrEmpty()) {
save(Settings())
}
}
}
override suspend fun save(t : Settings) {
boxStore.awaitCallInTx {
box.put(t)
}
}
override suspend fun saveAll(t : List<Settings>) {
boxStore.awaitCallInTx {
box.put(t)
}
}
override suspend fun getById(id: Long): Settings? {
return boxStore.awaitCallInTx {
box[id]
}
}
override suspend fun getAll(): List<Settings>? {
return boxStore.awaitCallInTx {
box.all
}
}
override suspend fun delete(t : Settings): Boolean? {
return boxStore.awaitCallInTx {
box.remove(t)
}
}
override suspend fun count() : Long? {
return boxStore.awaitCallInTx {
box.count()
}
}
}

View File

@ -0,0 +1,34 @@
package com.zaneschepke.wireguardautotunnel.repository
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import kotlinx.coroutines.flow.Flow
@Dao
interface SettingsDoa {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(t: Settings)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveAll(t: List<Settings>)
@Query("SELECT * FROM settings WHERE id=:id")
suspend fun getById(id: Long): Settings?
@Query("SELECT * FROM settings")
suspend fun getAll(): List<Settings>
@Query("SELECT * FROM settings")
fun getAllFlow(): Flow<MutableList<Settings>>
@Delete
suspend fun delete(t: Settings)
@Query("SELECT COUNT('id') FROM settings")
suspend fun count(): Long
}

View File

@ -1,57 +0,0 @@
package com.zaneschepke.wireguardautotunnel.repository
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import io.objectbox.Box
import io.objectbox.BoxStore
import io.objectbox.kotlin.awaitCallInTx
import io.objectbox.kotlin.toFlow
import kotlinx.coroutines.ExperimentalCoroutinesApi
import timber.log.Timber
import javax.inject.Inject
class TunnelBox @Inject constructor(private val box : Box<TunnelConfig>,private val boxStore : BoxStore) : Repository<TunnelConfig> {
@OptIn(ExperimentalCoroutinesApi::class)
override val itemFlow = box.query().build().subscribe().toFlow()
override fun init() {
}
override suspend fun save(t : TunnelConfig) {
Timber.d("Saving tunnel config")
boxStore.awaitCallInTx {
box.put(t)
}
}
override suspend fun saveAll(t : List<TunnelConfig>) {
boxStore.awaitCallInTx {
box.put(t)
}
}
override suspend fun getById(id: Long): TunnelConfig? {
return boxStore.awaitCallInTx {
box[id]
}
}
override suspend fun getAll(): List<TunnelConfig>? {
return boxStore.awaitCallInTx {
box.all
}
}
override suspend fun delete(t : TunnelConfig): Boolean? {
return boxStore.awaitCallInTx {
box.remove(t)
}
}
override suspend fun count() : Long? {
return boxStore.awaitCallInTx {
box.count()
}
}
}

View File

@ -0,0 +1,34 @@
package com.zaneschepke.wireguardautotunnel.repository
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import kotlinx.coroutines.flow.Flow
@Dao
interface TunnelConfigDao{
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(t: TunnelConfig)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveAll(t: List<TunnelConfig>)
@Query("SELECT * FROM TunnelConfig WHERE id=:id")
suspend fun getById(id: Long): TunnelConfig?
@Query("SELECT * FROM TunnelConfig")
suspend fun getAll(): List<TunnelConfig>
@Delete
suspend fun delete(t: TunnelConfig)
@Query("SELECT COUNT('id') FROM TunnelConfig")
suspend fun count(): Long
@Query("SELECT * FROM tunnelconfig")
fun getAllFlow(): Flow<MutableList<TunnelConfig>>
}

View File

@ -0,0 +1,15 @@
package com.zaneschepke.wireguardautotunnel.repository.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Settings(
@PrimaryKey(autoGenerate = true) val id : Int = 0,
@ColumnInfo(name = "is_tunnel_enabled") var isAutoTunnelEnabled : Boolean = false,
@ColumnInfo(name = "is_tunnel_on_mobile_data_enabled") var isTunnelOnMobileDataEnabled : Boolean = false,
@ColumnInfo(name = "trusted_network_ssids") var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
@ColumnInfo(name = "default_tunnel") var defaultTunnel : String? = null,
@ColumnInfo(name = "is_always_on_vpn_enabled") var isAlwaysOnVpnEnabled : Boolean = false,
)

View File

@ -1,25 +1,21 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel.model
package com.zaneschepke.wireguardautotunnel.repository.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import com.wireguard.config.Config
import io.objectbox.annotation.ConflictStrategy
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.annotation.Unique
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.io.InputStream
@Entity
@Entity(indices = [Index(value = ["name"], unique = true)])
@Serializable
data class TunnelConfig(
@Id
var id : Long = 0,
@Unique(onConflict = ConflictStrategy.REPLACE)
var name : String,
var wgQuick : String
) {
@PrimaryKey(autoGenerate = true) val id : Int = 0,
@ColumnInfo(name = "name") var name : String,
@ColumnInfo(name = "wg_quick") var wgQuick : String,
){
override fun toString(): String {
return Json.encodeToString(serializer(), this)

View File

@ -5,9 +5,8 @@ import android.app.Service
import android.content.Context
import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.zaneschepke.wireguardautotunnel.R
import timber.log.Timber
object ServiceManager {
@Suppress("DEPRECATION")
@ -43,7 +42,7 @@ object ServiceManager {
Action.STOP -> context.startService(intent)
}
} catch (e : Exception) {
e.message?.let { Firebase.crashlytics.log(it) }
Timber.e(e.message)
}
}

View File

@ -10,14 +10,14 @@ import android.os.SystemClock
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
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.Dispatchers
@ -39,7 +39,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
lateinit var mobileDataService : NetworkService<MobileDataService>
@Inject
lateinit var settingsRepo: Repository<Settings>
lateinit var settingsRepo: SettingsDoa
@Inject
lateinit var notificationService : NotificationService
@ -131,7 +131,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private fun startWatcherJob() {
watcherJob = CoroutineScope(Dispatchers.IO).launch {
val settings = settingsRepo.getAll();
if(!settings.isNullOrEmpty()) {
if(settings.isNotEmpty()) {
setting = settings[0]
}
launch {

View File

@ -5,12 +5,11 @@ import android.content.Intent
import android.os.Bundle
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
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.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -28,7 +27,7 @@ class WireGuardTunnelService : ForegroundService() {
lateinit var vpnService : VpnService
@Inject
lateinit var settingsRepo: Repository<Settings>
lateinit var settingsRepo: SettingsDoa
@Inject
lateinit var notificationService : NotificationService
@ -62,7 +61,7 @@ class WireGuardTunnelService : ForegroundService() {
} else {
Timber.d("Tunnel config null, starting default tunnel")
val settings = settingsRepo.getAll();
if(!settings.isNullOrEmpty()) {
if(settings.isNotEmpty()) {
val setting = settings[0]
if(setting.defaultTunnel != null && setting.isAlwaysOnVpnEnabled) {
val tunnelConfig = TunnelConfig.from(setting.defaultTunnel!!)

View File

@ -3,11 +3,10 @@ package com.zaneschepke.wireguardautotunnel.service.shortcut
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
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.Dispatchers
@ -18,14 +17,14 @@ import javax.inject.Inject
class ShortcutsActivity : AppCompatActivity() {
@Inject
lateinit var settingsRepo : Repository<Settings>
lateinit var settingsRepo : SettingsDoa
private val scope = CoroutineScope(Dispatchers.Main);
private fun attemptWatcherServiceToggle(tunnelConfig : String) {
scope.launch {
val settings = settingsRepo.getAll()
if (!settings.isNullOrEmpty()) {
if (settings.isNotEmpty()) {
val setting = settings.first()
if(setting.isAutoTunnelEnabled) {
ServiceManager.toggleWatcherServiceForeground(this@ShortcutsActivity, tunnelConfig)

View File

@ -8,7 +8,7 @@ import androidx.core.graphics.drawable.IconCompat
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
object ShortcutsManager {

View File

@ -5,11 +5,11 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
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.Dispatchers
@ -23,10 +23,10 @@ import javax.inject.Inject
class TunnelControlTile : TileService() {
@Inject
lateinit var settingsRepo : Repository<Settings>
lateinit var settingsRepo : SettingsDoa
@Inject
lateinit var configRepo : Repository<TunnelConfig>
lateinit var configRepo : TunnelConfigDao
@Inject
lateinit var vpnService : VpnService

View File

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

View File

@ -6,7 +6,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers

View File

@ -1,15 +0,0 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel.model
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
@Entity
data class Settings(
@Id
var id : Long = 0,
var isAutoTunnelEnabled : Boolean = false,
var isTunnelOnMobileDataEnabled : Boolean = false,
var trustedNetworkSSIDs : MutableList<String> = mutableListOf(),
var defaultTunnel : String? = null,
var isAlwaysOnVpnEnabled : Boolean = false,
)

View File

@ -33,6 +33,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.GoBackend
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.common.PermissionRequestFailedScreen
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
@ -143,12 +144,12 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) {
Routes.Settings.name, Routes.Support.name ->
slideInHorizontally(
initialOffsetX = { -1000 },
animationSpec = tween(500)
initialOffsetX = { -Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
)
else -> {
fadeIn(animationSpec = tween(1000))
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
}
}
}) {
@ -158,19 +159,19 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) {
Routes.Main.name ->
slideInHorizontally(
initialOffsetX = { 1000 },
animationSpec = tween(500)
initialOffsetX = { Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
)
Routes.Support.name -> {
slideInHorizontally(
initialOffsetX = { -1000 },
animationSpec = tween(500)
initialOffsetX = { -Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
)
}
else -> {
fadeIn(animationSpec = tween(1000))
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
}
}
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) }
@ -178,20 +179,20 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) {
Routes.Settings.name, Routes.Main.name ->
slideInHorizontally(
initialOffsetX = { 1000 },
animationSpec = tween(500)
initialOffsetX = { Constants.SLIDE_IN_ANIMATION_DURATION },
animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
)
else -> {
fadeIn(animationSpec = tween(1000))
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
}
}
}) { SupportScreen(padding = padding, focusRequester) }
composable("${Routes.Config.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000))
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
}) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)}
composable("${Routes.Detail.name}/{id}", enterTransition = {
fadeIn(animationSpec = tween(1000))
fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
}) { DetailScreen(padding = padding, id = it.arguments?.getString("id")) }
}
}

View File

@ -9,10 +9,10 @@ import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -22,8 +22,8 @@ import javax.inject.Inject
@HiltViewModel
class ConfigViewModel @Inject constructor(private val application : Application,
private val tunnelRepo : Repository<TunnelConfig>,
private val settingsRepo : Repository<Settings>) : ViewModel() {
private val tunnelRepo : TunnelConfigDao,
private val settingsRepo : SettingsDoa) : ViewModel() {
private val _tunnel = MutableStateFlow<TunnelConfig?>(null)
private val _tunnelName = MutableStateFlow("")
@ -140,14 +140,15 @@ class ConfigViewModel @Inject constructor(private val application : Application,
tunnelRepo.save(it)
ShortcutsManager.createTunnelShortcuts(application, 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()
))
}
if(settings.isEmpty()) {
return
}
val setting = settings[0]
if(setting.defaultTunnel != null) {
if(it.id == TunnelConfig.from(setting.defaultTunnel!!).id) {
settingsRepo.save(setting.copy(
defaultTunnel = it.toString()
))
}
}
}

View File

@ -2,9 +2,9 @@ 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.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
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
@ -12,7 +12,7 @@ import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class DetailViewModel @Inject constructor(private val tunnelRepo : Repository<TunnelConfig>, private val vpnService : VpnService
class DetailViewModel @Inject constructor(private val tunnelRepo : TunnelConfigDao, private val vpnService : VpnService
) : ViewModel() {

View File

@ -18,7 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FileOpen
@ -73,8 +73,8 @@ import androidx.navigation.NavController
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
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
@ -249,7 +249,7 @@ fun MainScreen(
.fillMaxSize()
.nestedScroll(nestedScrollConnection),
) {
itemsIndexed(tunnels.toList()) { _, tunnel ->
items(tunnels, key = { tunnel -> tunnel.id }) {tunnel ->
val focusRequester = FocusRequester();
RowListItem(leadingIcon = Icons.Rounded.Circle,
leadingIconColor = if (tunnelName == tunnel.name) when (handshakeStatus) {

View File

@ -9,21 +9,26 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wireguard.config.BadConfigException
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.ViewState
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -31,15 +36,15 @@ import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(private val application : Application,
private val tunnelRepo : Repository<TunnelConfig>,
private val settingsRepo : Repository<Settings>,
private val tunnelRepo : TunnelConfigDao,
private val settingsRepo : SettingsDoa,
private val vpnService: VpnService,
private val codeScanner: CodeScanner
) : ViewModel() {
private val _viewState = MutableStateFlow(ViewState())
val viewState get() = _viewState.asStateFlow()
val tunnels get() = tunnelRepo.itemFlow
val tunnels get() = tunnelRepo.getAllFlow()
val state get() = vpnService.state
val handshakeStatus get() = vpnService.handshakeStatus
@ -47,14 +52,9 @@ class MainViewModel @Inject constructor(private val application : Application,
private val _settings = MutableStateFlow(Settings())
val settings get() = _settings.asStateFlow()
private val defaultConfigName = {
"tunnel${(Math.random() * 100000).toInt()}"
}
init {
viewModelScope.launch {
settingsRepo.itemFlow.collect {
viewModelScope.launch(Dispatchers.IO) {
settingsRepo.getAllFlow().filter { it.isNotEmpty() }.collect {
val settings = it.first()
validateWatcherServiceState(settings)
_settings.emit(settings)
@ -75,7 +75,7 @@ class MainViewModel @Inject constructor(private val application : Application,
if(tunnelRepo.count() == 1L) {
ServiceManager.stopWatcherService(application.applicationContext)
val settings = settingsRepo.getAll()
if(!settings.isNullOrEmpty()) {
if(settings.isNotEmpty()) {
val setting = settings[0]
setting.defaultTunnel = null
setting.isAutoTunnelEnabled = false
@ -99,7 +99,7 @@ class MainViewModel @Inject constructor(private val application : Application,
suspend fun onTunnelQRSelected() {
codeScanner.scan().collect {
if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.config_validation))) {
val tunnelConfig = TunnelConfig(name = defaultConfigName(), wgQuick = it)
val tunnelConfig = TunnelConfig(name = NumberUtils.generateRandomTunnelName(), wgQuick = it)
saveTunnel(tunnelConfig)
} else if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.barcode_downloading))) {
showSnackBarMessage(application.resources.getString(R.string.barcode_downloading_message))
@ -110,34 +110,34 @@ class MainViewModel @Inject constructor(private val application : Application,
}
fun onTunnelFileSelected(uri : Uri) {
try {
val fileName = getFileName(application.applicationContext, uri)
val extension = getFileExtensionFromFileName(fileName)
if(extension != ".conf") {
viewModelScope.launch {
showSnackBarMessage(application.resources.getString(R.string.file_extension_message))
viewModelScope.launch(Dispatchers.IO) {
try {
val fileName = getFileName(application.applicationContext, uri)
val extension = getFileExtensionFromFileName(fileName)
if (extension != ".conf") {
launch {
showSnackBarMessage(application.resources.getString(R.string.file_extension_message))
}
return@launch
}
return
}
val stream = application.applicationContext.contentResolver.openInputStream(uri)
stream ?: return
val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
val stream = application.applicationContext.contentResolver.openInputStream(uri)
stream ?: return@launch
val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
val config = Config.parse(bufferReader)
val tunnelName = getNameFromFileName(fileName)
saveTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
stream.close()
} catch(_: BadConfigException) {
viewModelScope.launch {
showSnackBarMessage(application.applicationContext.getString(R.string.bad_config))
stream.close()
} catch (_: BadConfigException) {
launch {
showSnackBarMessage(application.applicationContext.getString(R.string.bad_config))
}
}
}
}
private fun saveTunnel(tunnelConfig : TunnelConfig) {
viewModelScope.launch {
tunnelRepo.save(tunnelConfig)
ShortcutsManager.createTunnelShortcuts(application.applicationContext, tunnelConfig)
}
private suspend fun saveTunnel(tunnelConfig : TunnelConfig) {
tunnelRepo.save(tunnelConfig)
ShortcutsManager.createTunnelShortcuts(application.applicationContext, tunnelConfig)
}
@SuppressLint("Range")
@ -149,14 +149,14 @@ class MainViewModel @Inject constructor(private val application : Application,
Timber.d("Exception getting config name")
null
}
cursor ?: return defaultConfigName()
cursor ?: return NumberUtils.generateRandomTunnelName()
cursor.use {
if(cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
}
return defaultConfigName()
return NumberUtils.generateRandomTunnelName()
}
suspend fun showSnackBarMessage(message : String) {
@ -170,7 +170,7 @@ class MainViewModel @Inject constructor(private val application : Application,
}
}
))
delay(3000)
delay(Constants.SNACKBAR_DELAY)
dismissSnackBar()
}

View File

@ -69,7 +69,7 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import kotlinx.coroutines.launch

View File

@ -6,35 +6,41 @@ import android.location.LocationManager
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.repository.model.Settings
import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.ViewState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(private val application : Application,
private val tunnelRepo : Repository<TunnelConfig>, private val settingsRepo : Repository<Settings>
private val tunnelRepo : TunnelConfigDao, private val settingsRepo : SettingsDoa
) : ViewModel() {
private val _trustedSSIDs = MutableStateFlow(emptyList<String>())
val trustedSSIDs = _trustedSSIDs.asStateFlow()
private val _settings = MutableStateFlow(Settings())
val settings get() = _settings.asStateFlow()
val tunnels get() = tunnelRepo.itemFlow
val tunnels get() = tunnelRepo.getAllFlow()
private val _viewState = MutableStateFlow(ViewState())
val viewState get() = _viewState.asStateFlow()
init {
checkLocationServicesEnabled()
viewModelScope.launch {
settingsRepo.itemFlow.collect {
viewModelScope.launch(Dispatchers.IO) {
settingsRepo.getAllFlow().filter { it.isNotEmpty() }.collect {
val settings = it.first()
_settings.emit(settings)
_trustedSSIDs.emit(settings.trustedNetworkSSIDs.toList())

View File

@ -13,6 +13,10 @@ object NumberUtils {
return bytes.toBigDecimal().divide(BYTES_IN_KB.toBigDecimal())
}
fun generateRandomTunnelName() : String {
return "tunnel${(Math.random() * 100000).toInt()}"
}
fun formatDecimalTwoPlaces(bigDecimal: BigDecimal) : String {
val df = DecimalFormat("#.##")
return df.format(bigDecimal)

View File

@ -91,4 +91,5 @@
<string name="search_icon">Search Icon</string>
<string name="attempt_connection">Attempting connection..</string>
<string name="vpn_starting">VPN Starting</string>
<string name="db_name">wg-tunnel-db</string>
</resources>

View File

@ -1,20 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
val objectBoxVersion by extra("3.7.0")
val hiltVersion by extra("2.48")
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.9")
if (BuildHelper.isReleaseBuild(gradle) && BuildHelper.isGeneralFlavor(gradle)) {
classpath(libs.google.services)
classpath(libs.firebase.crashlytics.gradle)
}
}
}
plugins {
id("com.android.application") version "8.2.0-beta03" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
id("com.google.dagger.hilt.android") version "2.48" apply false
kotlin("plugin.serialization") version "1.8.22" apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.hilt.android) apply false
kotlin("plugin.serialization").version(libs.versions.kotlin).apply(false)
alias(libs.plugins.ksp) apply false
}

View File

@ -0,0 +1,8 @@
plugins {
`kotlin-dsl` // enable the Kotlin-DSL
}
repositories {
google()
mavenCentral()
}

View File

@ -0,0 +1,29 @@
import org.gradle.api.invocation.Gradle
object BuildHelper {
private fun getCurrentFlavor(gradle : Gradle): String {
val taskRequestsStr = gradle.startParameter.taskRequests.toString()
val pattern: java.util.regex.Pattern = if (taskRequestsStr.contains("assemble")) {
java.util.regex.Pattern.compile("assemble(\\w+)(Release|Debug)")
} else {
java.util.regex.Pattern.compile("bundle(\\w+)(Release|Debug)")
}
val matcher = pattern.matcher(taskRequestsStr)
val flavor = if (matcher.find()) {
matcher.group(1).lowercase()
} else {
print("NO FLAVOR FOUND")
""
}
return flavor
}
fun isGeneralFlavor(gradle : Gradle) : Boolean {
return getCurrentFlavor(gradle) == "general"
}
fun isReleaseBuild(gradle: Gradle) : Boolean {
return (gradle.startParameter.taskNames.size > 0 && gradle.startParameter.taskNames[0].contains(
"Release"))
}
}

View File

@ -21,5 +21,3 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#enable buildconfig values
android.defaults.buildfeatures.buildconfig=true

89
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,89 @@
[versions]
accompanist = "0.31.2-alpha"
activityCompose = "1.7.2"
androidx-junit = "1.1.5"
appcompat = "1.6.1"
coreKtx = "1.12.0"
espressoCore = "3.5.1"
firebase-crashlytics-gradle = "2.9.9"
google-services = "4.3.15"
hiltAndroid = "2.48"
hiltNavigationCompose = "1.0.0"
junit = "4.13.2"
kotlinx-serialization-json = "1.5.1"
lifecycle-runtime-compose = "2.6.2"
material-icons-extended = "1.5.1"
material3 = "1.1.1"
navigationCompose = "2.7.2"
playServicesCodeScanner = "16.1.0"
roomVersion = "2.6.0-beta01"
timber = "5.0.1"
tunnel = "1.0.20230706"
androidGradlePlugin = "8.2.0-beta03"
kotlin="1.9.10"
ksp="1.9.10-1.0.13"
composeBom="2023.09.00"
firebaseBom="32.2.3"
compose="1.5.1"
crashlytics="18.4.1"
analytics="21.3.0"
composeCompiler="1.5.3"
[libraries]
# accompanist
accompanist-drawablepainter = { module = "com.google.accompanist:accompanist-drawablepainter", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-navigation-animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
#room
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomVersion" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomVersion" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomVersion" }
#compose
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref="composeBom" }
androidx-compose-ui-test = { module="androidx.compose.ui:ui-test-junit4", version.ref="compose" }
androidx-compose-ui-tooling = { module="androidx.compose.ui:ui-tooling", version.ref="compose" }
androidx-compose-manifest = { module="androidx.compose.ui:ui-test-manifest", version.ref="compose" }
androidx-compose-ui-graphics = { module="androidx.compose.ui:ui-graphics", version.ref="compose" }
androidx-compose-ui-tooling-preview = { module="androidx.compose.ui:ui-tooling-preview", version.ref="compose" }
androidx-compose-ui = { module="androidx.compose.ui:ui", version.ref="compose" }
#hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-compose" }
androidx-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle-runtime-compose" }
material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "material-icons-extended" }
play-services-code-scanner = { module = "com.google.android.gms:play-services-code-scanner", version.ref = "playServicesCodeScanner" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
tunnel = { module = "com.wireguard.android:tunnel", version.ref = "tunnel" }
#firebase
google-firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx", version.ref = "crashlytics" }
google-firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx", version.ref = "analytics" }
firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebase-crashlytics-gradle" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom"}
google-services = { module = "com.google.gms:google-services", version.ref = "google-services" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hiltAndroid" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }