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

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 <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="barcode_ui"/> android:value="barcode_ui"/>
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="true" />
</application> </application>
</manifest> </manifest>

View File

@ -1,6 +1,11 @@
package com.zaneschepke.wireguardautotunnel package com.zaneschepke.wireguardautotunnel
object Constants { object Constants {
const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L; const val VPN_CONNECTIVITY_CHECK_INTERVAL = 3000L
const val VPN_STATISTIC_CHECK_INTERVAL = 10000L; 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.app.Application
import android.content.Context import android.content.Context
import android.content.pm.PackageManager 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 dagger.hilt.android.HiltAndroidApp
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject
@HiltAndroidApp @HiltAndroidApp
class WireGuardAutoTunnel : Application() { class WireGuardAutoTunnel : Application() {
@Inject
lateinit var settingsRepo : Repository<Settings>
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if(BuildConfig.DEBUG) { if(BuildConfig.DEBUG) {
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false);
Timber.plant(Timber.DebugTree()) Timber.plant(Timber.DebugTree())
} }
try {
MlKit.initialize(this)
} catch (e : Exception) {
Timber.e(e.message)
}
settingsRepo.init()
} }
companion object { 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 package com.zaneschepke.wireguardautotunnel.module
import com.zaneschepke.wireguardautotunnel.repository.Repository import com.zaneschepke.wireguardautotunnel.repository.AppDatabase
import com.zaneschepke.wireguardautotunnel.repository.SettingsBox import com.zaneschepke.wireguardautotunnel.repository.SettingsDoa
import com.zaneschepke.wireguardautotunnel.repository.TunnelBox import com.zaneschepke.wireguardautotunnel.repository.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
abstract class RepositoryModule { class RepositoryModule {
@Binds
@Singleton @Singleton
abstract fun provideSettingsRepository(settingsBox: SettingsBox) : Repository<Settings> @Provides
fun provideSettingsRepository(appDatabase: AppDatabase) : SettingsDoa {
return appDatabase.settingDao()
}
@Binds
@Singleton @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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent 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.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -17,7 +16,7 @@ import javax.inject.Inject
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
@Inject @Inject
lateinit var settingsRepo : Repository<Settings> lateinit var settingsRepo : SettingsDoa
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) { 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.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent 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.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -18,16 +18,16 @@ import javax.inject.Inject
class NotificationActionReceiver : BroadcastReceiver() { class NotificationActionReceiver : BroadcastReceiver() {
@Inject @Inject
lateinit var settingsRepo : Repository<Settings> lateinit var settingsRepo : SettingsDoa
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val settings = settingsRepo.getAll() val settings = settingsRepo.getAll()
if (!settings.isNullOrEmpty()) { if (settings.isNotEmpty()) {
val setting = settings.first() val setting = settings.first()
if (setting.defaultTunnel != null) { if (setting.defaultTunnel != null) {
ServiceManager.stopVpnService(context) ServiceManager.stopVpnService(context)
delay(1000) delay(Constants.TOGGLE_TUNNEL_DELAY)
ServiceManager.startVpnService(context, setting.defaultTunnel.toString()) 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 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.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.InputStream import java.io.InputStream
@Entity(indices = [Index(value = ["name"], unique = true)])
@Entity
@Serializable @Serializable
data class TunnelConfig( data class TunnelConfig(
@Id @PrimaryKey(autoGenerate = true) val id : Int = 0,
var id : Long = 0, @ColumnInfo(name = "name") var name : String,
@Unique(onConflict = ConflictStrategy.REPLACE) @ColumnInfo(name = "wg_quick") var wgQuick : String,
var name : String, ){
var wgQuick : String
) {
override fun toString(): String { override fun toString(): String {
return Json.encodeToString(serializer(), this) return Json.encodeToString(serializer(), this)

View File

@ -5,9 +5,8 @@ import android.app.Service
import android.content.Context import android.content.Context
import android.content.Context.ACTIVITY_SERVICE import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent import android.content.Intent
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import timber.log.Timber
object ServiceManager { object ServiceManager {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@ -43,7 +42,7 @@ object ServiceManager {
Action.STOP -> context.startService(intent) Action.STOP -> context.startService(intent)
} }
} catch (e : Exception) { } 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.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.Constants import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.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.MobileDataService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
import com.zaneschepke.wireguardautotunnel.service.network.WifiService import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -39,7 +39,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
lateinit var mobileDataService : NetworkService<MobileDataService> lateinit var mobileDataService : NetworkService<MobileDataService>
@Inject @Inject
lateinit var settingsRepo: Repository<Settings> lateinit var settingsRepo: SettingsDoa
@Inject @Inject
lateinit var notificationService : NotificationService lateinit var notificationService : NotificationService
@ -131,7 +131,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private fun startWatcherJob() { private fun startWatcherJob() {
watcherJob = CoroutineScope(Dispatchers.IO).launch { watcherJob = CoroutineScope(Dispatchers.IO).launch {
val settings = settingsRepo.getAll(); val settings = settingsRepo.getAll();
if(!settings.isNullOrEmpty()) { if(settings.isNotEmpty()) {
setting = settings[0] setting = settings[0]
} }
launch { launch {

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel import com.wireguard.android.backend.Tunnel
import com.wireguard.crypto.Key import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.Constants 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 com.zaneschepke.wireguardautotunnel.util.NumberUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers 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.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.wireguard.android.backend.GoBackend import com.wireguard.android.backend.GoBackend
import com.zaneschepke.wireguardautotunnel.Constants
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.common.PermissionRequestFailedScreen import com.zaneschepke.wireguardautotunnel.ui.common.PermissionRequestFailedScreen
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavBar
@ -143,12 +144,12 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) { when (initialState.destination.route) {
Routes.Settings.name, Routes.Support.name -> Routes.Settings.name, Routes.Support.name ->
slideInHorizontally( slideInHorizontally(
initialOffsetX = { -1000 }, initialOffsetX = { -Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(500) animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
) )
else -> { else -> {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
} }
} }
}) { }) {
@ -158,19 +159,19 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) { when (initialState.destination.route) {
Routes.Main.name -> Routes.Main.name ->
slideInHorizontally( slideInHorizontally(
initialOffsetX = { 1000 }, initialOffsetX = { Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(500) animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
) )
Routes.Support.name -> { Routes.Support.name -> {
slideInHorizontally( slideInHorizontally(
initialOffsetX = { -1000 }, initialOffsetX = { -Constants.SLIDE_IN_TRANSITION_OFFSET },
animationSpec = tween(500) animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
) )
} }
else -> { else -> {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
} }
} }
}) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) } }) { SettingsScreen(padding = padding, snackbarHostState = snackbarHostState, navController = navController, focusRequester = focusRequester) }
@ -178,20 +179,20 @@ class MainActivity : AppCompatActivity() {
when (initialState.destination.route) { when (initialState.destination.route) {
Routes.Settings.name, Routes.Main.name -> Routes.Settings.name, Routes.Main.name ->
slideInHorizontally( slideInHorizontally(
initialOffsetX = { 1000 }, initialOffsetX = { Constants.SLIDE_IN_ANIMATION_DURATION },
animationSpec = tween(500) animationSpec = tween(Constants.SLIDE_IN_ANIMATION_DURATION)
) )
else -> { else -> {
fadeIn(animationSpec = tween(1000)) fadeIn(animationSpec = tween(Constants.FADE_IN_ANIMATION_DURATION))
} }
} }
}) { SupportScreen(padding = padding, focusRequester) } }) { SupportScreen(padding = padding, focusRequester) }
composable("${Routes.Config.name}/{id}", enterTransition = { 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)} }) { ConfigScreen(padding = padding, navController = navController, id = it.arguments?.getString("id"), focusRequester = focusRequester)}
composable("${Routes.Detail.name}/{id}", enterTransition = { 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")) } }) { 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.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings import com.zaneschepke.wireguardautotunnel.repository.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -22,8 +22,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ConfigViewModel @Inject constructor(private val application : Application, class ConfigViewModel @Inject constructor(private val application : Application,
private val tunnelRepo : Repository<TunnelConfig>, private val tunnelRepo : TunnelConfigDao,
private val settingsRepo : Repository<Settings>) : ViewModel() { private val settingsRepo : SettingsDoa) : ViewModel() {
private val _tunnel = MutableStateFlow<TunnelConfig?>(null) private val _tunnel = MutableStateFlow<TunnelConfig?>(null)
private val _tunnelName = MutableStateFlow("") private val _tunnelName = MutableStateFlow("")
@ -140,14 +140,15 @@ class ConfigViewModel @Inject constructor(private val application : Application,
tunnelRepo.save(it) tunnelRepo.save(it)
ShortcutsManager.createTunnelShortcuts(application, it) ShortcutsManager.createTunnelShortcuts(application, it)
val settings = settingsRepo.getAll() val settings = settingsRepo.getAll()
if(settings != null) { if(settings.isEmpty()) {
val setting = settings[0] return
if(setting.defaultTunnel != null) { }
if(it.id == TunnelConfig.from(setting.defaultTunnel!!).id) { val setting = settings[0]
settingsRepo.save(setting.copy( if(setting.defaultTunnel != null) {
defaultTunnel = it.toString() 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 androidx.lifecycle.ViewModel
import com.wireguard.config.Config 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.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -12,7 +12,7 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @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() { ) : ViewModel() {

View File

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

View File

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

View File

@ -69,7 +69,7 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel 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.Routes
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

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

View File

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

View File

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

View File

@ -1,20 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
val objectBoxVersion by extra("3.7.0")
val hiltVersion by extra("2.48")
val accompanistVersion by extra("0.31.2-alpha")
dependencies { dependencies {
classpath("io.objectbox:objectbox-gradle-plugin:$objectBoxVersion") if (BuildHelper.isReleaseBuild(gradle) && BuildHelper.isGeneralFlavor(gradle)) {
classpath("com.google.gms:google-services:4.3.15") classpath(libs.google.services)
classpath("com.google.firebase:firebase-crashlytics-gradle:2.9.9") classpath(libs.firebase.crashlytics.gradle)
}
} }
} }
plugins { plugins {
id("com.android.application") version "8.2.0-beta03" apply false alias(libs.plugins.android.application) apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false alias(libs.plugins.kotlin.android) apply false
id("com.google.dagger.hilt.android") version "2.48" apply false alias(libs.plugins.hilt.android) apply false
kotlin("plugin.serialization") version "1.8.22" 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, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true 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" }