feat: migrate from hilt to koin for DI

This commit is contained in:
infi 2026-05-03 19:43:07 +02:00
parent 10efd73965
commit 68e0b56afa
22 changed files with 141 additions and 150 deletions

View File

@ -1,20 +1,18 @@
import com.mikepenz.aboutlibraries.plugin.StrictMode
import io.sentry.android.gradle.instrumentation.logcat.LogcatLevel
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.aboutlibraries)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.sentry.android)
alias(libs.plugins.sqldelight)
alias(libs.plugins.google.services)
id("kotlin-kapt")
id("kotlin-parcelize")
}
@ -128,9 +126,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
compose = true
@ -156,6 +151,12 @@ android {
}
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_17
}
}
sentry {
autoUploadProguardMapping =
buildproperty("sentry.upload_mappings", "RVX_SENTRY_UPLOAD_MAPPINGS") == "true"
@ -209,10 +210,6 @@ dependencies {
debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.compose.ui.test.manifest)
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
ksp(libs.hilt.compiler)
implementation(libs.glide)
implementation(libs.glide.compose)
ksp(libs.glide.ksp)
@ -272,6 +269,10 @@ dependencies {
implementation(libs.square.logcat)
implementation(libs.koin)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel.navigation)
androidTestImplementation(libs.android.test.core)
androidTestImplementation(libs.android.test.rules)
androidTestImplementation(libs.compose.ui.test.junit4)
@ -283,15 +284,16 @@ aboutLibraries {
strictMode = StrictMode.FAIL
allowedLicenses += listOf(
"Apache-2.0",
"OFL",
"MIT",
"ASDKL",
"BSD-2-Clause",
"BSD-3-Clause", "The 3-Clause BSD License",
"BSD License",
"cmark",
"EPL-1.0",
"BSD-3-Clause",
"BSD License",
"ML Kit Terms of Service"
"MIT",
"ML Kit Terms of Service",
"OFL",
"Public Domain"
)
configPath = "compliance"
}

View File

@ -3,12 +3,15 @@ package chat.stoat
import android.app.Application
import android.os.Build
import android.os.StrictMode
import chat.stoat.di.appModule
import chat.stoat.di.viewModelModule
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.HiltAndroidApp
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
@HiltAndroidApp
class StoatApplication : Application() {
companion object {
lateinit var instance: StoatApplication
@ -18,6 +21,12 @@ class StoatApplication : Application() {
super.onCreate()
AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.VERBOSE)
startKoin {
androidContext(this@StoatApplication)
androidLogger()
modules(appModule, viewModelModule)
}
if (BuildConfig.DEBUG) {
// Enable strict mode primarily to catch non-API usage, although we detect all
// violations for our reference.

View File

@ -1,6 +1,5 @@
package chat.stoat.activities
import android.annotation.SuppressLint
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
@ -15,7 +14,6 @@ import android.view.ViewTreeObserver
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.AnimatedVisibility
@ -126,20 +124,15 @@ import chat.stoat.screens.settings.channel.ChannelSettingsOverview
import chat.stoat.screens.settings.channel.ChannelSettingsPermissions
import chat.stoat.ui.theme.StoatTheme
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.ktor.client.request.get
import io.sentry.android.core.SentryAndroid
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.viewmodel.ext.android.viewModel
@HiltViewModel
@SuppressLint("StaticFieldLeak")
class MainActivityViewModel @Inject constructor(
class MainActivityViewModel(
private val kvStorage: KVStorage,
@ApplicationContext private val context: Context
private val context: Context
) : ViewModel() {
val nextDestination = MutableStateFlow<String?>(null)
var isConnected = MutableStateFlow(false)
@ -324,9 +317,8 @@ class MainActivityViewModel @Inject constructor(
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainActivityViewModel>()
private val viewModel: MainActivityViewModel by viewModel()
// Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours
// See the other one in DefaultDestinationScreen.kt

View File

@ -43,11 +43,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat
import androidx.documentfile.provider.DocumentFile
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import chat.stoat.R
@ -57,7 +57,6 @@ import chat.stoat.api.routes.channel.sendMessage
import chat.stoat.api.routes.microservices.autumn.FileArgs
import chat.stoat.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
import chat.stoat.api.routes.microservices.autumn.uploadToAutumn
import chat.stoat.core.model.schemas.ChannelType
import chat.stoat.api.settings.LoadedSettings
import chat.stoat.api.settings.SyncedSettings
import chat.stoat.composables.chat.MessageField
@ -66,17 +65,15 @@ import chat.stoat.composables.screens.chat.AttachmentManager
import chat.stoat.composables.screens.chat.drawer.ChannelItem
import chat.stoat.composables.screens.chat.drawer.ChannelItemIconType
import chat.stoat.composables.screens.chat.drawer.DMOrGroupItem
import chat.stoat.core.model.schemas.ChannelType
import chat.stoat.persistence.KVStorage
import chat.stoat.screens.chat.views.channel.ChannelScreenActivePane
import chat.stoat.ui.theme.StoatTheme
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import io.ktor.http.ContentType
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import java.io.File
import javax.inject.Inject
@AndroidEntryPoint
class ShareTargetActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -163,8 +160,7 @@ class ShareTargetActivity : ComponentActivity() {
}
}
@HiltViewModel
class ShareTargetScreenViewModel @Inject constructor(
class ShareTargetScreenViewModel(
private val kvStorage: KVStorage,
) : ViewModel() {
var apiIsReady by mutableStateOf(false)
@ -229,15 +225,16 @@ fun ShareTargetScreen(
text: String?,
media: List<Uri>?,
onFinished: () -> Unit = {},
viewModel: ShareTargetScreenViewModel = hiltViewModel()
viewModel: ShareTargetScreenViewModel = koinViewModel()
) {
val context = LocalContext.current
val resources = LocalResources.current
LaunchedEffect(Unit) {
if (!viewModel.isLoggedIn()) {
Toast.makeText(
context,
context.getString(R.string.share_target_login_first),
resources.getString(R.string.share_target_login_first),
Toast.LENGTH_SHORT
).show()
@ -412,7 +409,7 @@ fun ShareTargetScreen(
if (selectedChannel == null) {
Toast.makeText(
context,
context.getString(R.string.share_target_select_channel),
resources.getString(R.string.share_target_select_channel),
Toast.LENGTH_SHORT
).show()
return@MessageField

View File

@ -0,0 +1,10 @@
package chat.stoat.di
import chat.stoat.persistence.KVStorage
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
//
val appModule = module {
single { KVStorage(androidContext()) }
}

View File

@ -0,0 +1,35 @@
package chat.stoat.di
import chat.stoat.activities.MainActivityViewModel
import chat.stoat.activities.ShareTargetScreenViewModel
import chat.stoat.screens.chat.ChatRouterViewModel
import chat.stoat.screens.chat.views.channel.ChannelScreenViewModel
import chat.stoat.screens.login.LoginViewModel
import chat.stoat.screens.login.MfaScreenViewModel
import chat.stoat.screens.settings.AppearanceSettingsScreenViewModel
import chat.stoat.screens.settings.ChangelogsSettingsScreenViewModel
import chat.stoat.screens.settings.DebugSettingsScreenViewModel
import chat.stoat.screens.settings.ProfileSettingsScreenViewModel
import chat.stoat.screens.settings.SettingsScreenViewModel
import chat.stoat.screens.settings.channel.ChannelSettingsOverviewViewModel
import chat.stoat.sheets.MemberListSheetViewModel
import org.koin.android.ext.koin.androidApplication
import org.koin.android.ext.koin.androidContext
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
val viewModelModule = module {
viewModel { MainActivityViewModel(get(), androidContext()) }
viewModel { ChatRouterViewModel(get(), androidContext()) }
viewModel { MemberListSheetViewModel(androidApplication()) }
viewModel { ShareTargetScreenViewModel(get()) }
viewModel { ChannelScreenViewModel(get()) }
viewModel { MfaScreenViewModel(get()) }
viewModel { SettingsScreenViewModel(get()) }
viewModel { DebugSettingsScreenViewModel(get()) }
viewModel { LoginViewModel(get()) }
viewModel { ProfileSettingsScreenViewModel(androidApplication()) }
viewModel { ChangelogsSettingsScreenViewModel(get(), androidApplication()) }
viewModel { AppearanceSettingsScreenViewModel(androidApplication()) }
viewModel { ChannelSettingsOverviewViewModel(androidApplication()) }
}

View File

@ -6,16 +6,12 @@ import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.firstOrNull
import javax.inject.Inject
import javax.inject.Singleton
val Context.stoatKVStorage: DataStore<Preferences> by preferencesDataStore(name = "revolt_kv")
@Singleton
class KVStorage @Inject constructor(
@ApplicationContext private val mContext: Context
class KVStorage(
private val mContext: Context
) {
private val dataStore = mContext.stoatKVStorage

View File

@ -1,6 +1,5 @@
package chat.stoat.screens.chat
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.Log
@ -60,7 +59,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.core.app.NotificationManagerCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.LifecycleEventEffect
@ -101,12 +99,10 @@ import chat.stoat.sheets.WebHookUserSheet
import chat.stoat.sheets.spark.SwipeToReplySparkSheet
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.sentry.Sentry
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
sealed class ChatRouterDestination {
data object Overview : ChatRouterDestination()
@ -145,11 +141,9 @@ sealed class ChatRouterDestination {
}
}
@HiltViewModel
@SuppressLint("StaticFieldLeak")
class ChatRouterViewModel @Inject constructor(
class ChatRouterViewModel(
private val kvStorage: KVStorage,
@ApplicationContext val context: Context
val context: Context
) : ViewModel() {
var currentDestination by mutableStateOf<ChatRouterDestination>(ChatRouterDestination.default)
var latestChangelogRead by mutableStateOf(true)
@ -281,7 +275,7 @@ fun ChatRouterScreen(
disableBackHandler: Boolean,
onNullifiedUser: () -> Unit,
onEnterVoiceUI: (String) -> Unit,
viewModel: ChatRouterViewModel = hiltViewModel()
viewModel: ChatRouterViewModel = koinViewModel()
) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()

View File

@ -104,7 +104,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import androidx.documentfile.provider.DocumentFile
import androidx.hilt.navigation.compose.hiltViewModel
import chat.stoat.R
import chat.stoat.StoatApplication
import chat.stoat.activities.StoatTweenDp
@ -153,6 +152,7 @@ import com.valentinilk.shimmer.shimmer
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import org.koin.androidx.compose.koinViewModel
import java.io.File
import kotlin.math.max
@ -198,7 +198,7 @@ fun ChannelScreen(
drawerIsOpen: Boolean = false,
backButtonAction: (() -> Unit)? = null,
useChatUI: Boolean = false,
viewModel: ChannelScreenViewModel = hiltViewModel()
viewModel: ChannelScreenViewModel = koinViewModel()
) {
// <editor-fold desc="State and effects">
val scope = rememberCoroutineScope()

View File

@ -52,7 +52,6 @@ import chat.stoat.internals.text.MessageProcessor
import chat.stoat.persistence.KVStorage
import chat.stoat.screens.chat.ChatRouterDestination
import chat.stoat.settings.providers.AgeGateUnlockedStorageProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import io.ktor.http.ContentType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -66,10 +65,8 @@ import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.ZoneId
import javax.inject.Inject
@HiltViewModel
class ChannelScreenViewModel @Inject constructor(
class ChannelScreenViewModel(
private val kvStorage: KVStorage,
) : ViewModel() {
var items = mutableStateListOf<ChannelScreenItem>()

View File

@ -43,7 +43,6 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -58,12 +57,10 @@ import chat.stoat.composables.generic.FormTextField
import chat.stoat.composables.generic.Weblink
import chat.stoat.persistence.KVStorage
import chat.stoat.ui.theme.FragmentMono
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
@HiltViewModel
class LoginViewModel @Inject constructor(
class LoginViewModel(
private val kvStorage: KVStorage
) : ViewModel() {
private var _email by mutableStateOf("")
@ -151,7 +148,7 @@ class LoginViewModel @Inject constructor(
}
@Composable
fun LoginScreen(navController: NavController, viewModel: LoginViewModel = hiltViewModel()) {
fun LoginScreen(navController: NavController, viewModel: LoginViewModel = koinViewModel()) {
val passwordTextFieldState = rememberTextFieldState()
LaunchedEffect(passwordTextFieldState.text) {
viewModel.setPassword(passwordTextFieldState.text.toString())

View File

@ -32,7 +32,6 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -45,12 +44,10 @@ import chat.stoat.api.routes.account.authenticateWithMfaTotpCode
import chat.stoat.composables.generic.CollapsibleCard
import chat.stoat.composables.generic.FormTextField
import chat.stoat.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
@HiltViewModel
class MfaScreenViewModel @Inject constructor(
class MfaScreenViewModel(
private val kvStorage: KVStorage
) : ViewModel() {
private var _totpCode by mutableStateOf("")
@ -142,7 +139,7 @@ fun MfaScreen(
navController: NavController,
allowedAuthTypesCommaSep: String,
mfaTicket: String,
viewModel: MfaScreenViewModel = hiltViewModel()
viewModel: MfaScreenViewModel = koinViewModel()
) {
val allowedAuthTypes = allowedAuthTypesCommaSep.split(",")

View File

@ -1,6 +1,6 @@
package chat.stoat.screens.settings
import android.content.Context
import android.app.Application
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@ -50,6 +50,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -59,7 +60,6 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -82,20 +82,14 @@ import chat.stoat.ui.theme.getFieldByName
import chat.stoat.ui.theme.overridableColourSchemeFieldNameToResource
import chat.stoat.ui.theme.overridableColourSchemeFieldNames
import chat.stoat.ui.theme.systemSupportsDynamicColors
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import org.koin.androidx.compose.koinViewModel
import java.io.File
import javax.inject.Inject
@HiltViewModel
@Suppress("StaticFieldLeak")
class AppearanceSettingsScreenViewModel @Inject constructor(
@ApplicationContext val context: Context
) : ViewModel() {
class AppearanceSettingsScreenViewModel(val context: Application) : ViewModel() {
var showColourOverrides by mutableStateOf(false)
var selectedOverrideName by mutableStateOf<String?>(null)
var selectedOverrideInitialValue by mutableStateOf<Int?>(null)
@ -224,7 +218,7 @@ class AppearanceSettingsScreenViewModel @Inject constructor(
@Composable
fun AppearanceSettingsScreen(
navController: NavController,
viewModel: AppearanceSettingsScreenViewModel = hiltViewModel()
viewModel: AppearanceSettingsScreenViewModel = koinViewModel()
) {
val colourOverridesOpenerArrowRotation by animateFloatAsState(
if (viewModel.showColourOverrides) {
@ -249,6 +243,7 @@ fun AppearanceSettingsScreen(
}
val context = LocalContext.current
val resources = LocalResources.current
val scope = rememberCoroutineScope()
if (viewModel.overridePickerSheetVisible) {
@ -404,9 +399,7 @@ fun AppearanceSettingsScreen(
) {
Toast.makeText(
context,
context.getString(
R.string.settings_appearance_theme_m3dynamic_unsupported_toast
),
resources.getString(R.string.settings_appearance_theme_m3dynamic_unsupported_toast),
Toast.LENGTH_SHORT
).show()
}
@ -555,7 +548,7 @@ fun AppearanceSettingsScreen(
ColourChip(
color = Color(value ?: 0),
text = overridableColourSchemeFieldNameToResource[fieldName]
?.let { context.getString(it) }
?.let { stringResource(it) }
?: fieldName,
modifier = Modifier
.fillMaxWidth()

View File

@ -1,6 +1,6 @@
package chat.stoat.screens.settings
import android.content.Context
import android.app.Application
import android.text.format.DateUtils
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
@ -36,7 +36,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -45,16 +44,13 @@ import chat.stoat.internals.ChangelogIndex
import chat.stoat.internals.Changelogs
import chat.stoat.persistence.KVStorage
import chat.stoat.sheets.ChangelogSheet
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
@HiltViewModel
class ChangelogsSettingsScreenViewModel @Inject constructor(
class ChangelogsSettingsScreenViewModel(
val kvStorage: KVStorage,
@ApplicationContext val context: Context
val context: Application
) : ViewModel() {
var index by mutableStateOf<ChangelogIndex?>(null)
var renderedChangelog by mutableStateOf("")
@ -87,7 +83,7 @@ class ChangelogsSettingsScreenViewModel @Inject constructor(
@Composable
fun ChangelogsSettingsScreen(
navController: NavController,
viewModel: ChangelogsSettingsScreenViewModel = hiltViewModel()
viewModel: ChangelogsSettingsScreenViewModel = koinViewModel()
) {
LaunchedEffect(Unit) {
viewModel.populate()

View File

@ -42,7 +42,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -57,12 +56,10 @@ import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
@HiltViewModel
class DebugSettingsScreenViewModel @Inject constructor(
class DebugSettingsScreenViewModel(
private val kvStorage: KVStorage
) : ViewModel() {
fun forgetAllSparks() {
@ -103,7 +100,7 @@ class DebugSettingsScreenViewModel @Inject constructor(
@Composable
fun DebugSettingsScreen(
navController: NavController,
viewModel: DebugSettingsScreenViewModel = hiltViewModel()
viewModel: DebugSettingsScreenViewModel = koinViewModel()
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()

View File

@ -1,6 +1,6 @@
package chat.stoat.screens.settings
import android.content.Context
import android.app.Application
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
@ -42,7 +42,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -52,19 +51,15 @@ import chat.stoat.api.StoatAPI
import chat.stoat.api.routes.microservices.autumn.uploadToAutumn
import chat.stoat.api.routes.user.fetchUserProfile
import chat.stoat.api.routes.user.patchSelf
import chat.stoat.core.model.schemas.Profile
import chat.stoat.composables.generic.InlineMediaPicker
import chat.stoat.composables.screens.settings.RawUserOverview
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import chat.stoat.core.model.schemas.Profile
import io.ktor.http.ContentType
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import java.io.File
import javax.inject.Inject
@HiltViewModel
@Suppress("StaticFieldLeak")
class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val context: Context) :
class ProfileSettingsScreenViewModel(val context: Application) :
ViewModel() {
var isLoading by mutableStateOf(true)
var pfpModel by mutableStateOf<Any?>(null)
@ -237,7 +232,7 @@ class ProfileSettingsScreenViewModel @Inject constructor(@ApplicationContext val
@Composable
fun ProfileSettingsScreen(
navController: NavController,
viewModel: ProfileSettingsScreenViewModel = hiltViewModel()
viewModel: ProfileSettingsScreenViewModel = koinViewModel()
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

View File

@ -29,7 +29,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
import chat.stoat.BuildConfig
@ -40,12 +39,10 @@ import chat.stoat.api.settings.FeatureFlags
import chat.stoat.api.settings.LoadedSettings
import chat.stoat.composables.generic.ListHeader
import chat.stoat.persistence.KVStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.runBlocking
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
@HiltViewModel
class SettingsScreenViewModel @Inject constructor(
class SettingsScreenViewModel(
private val kvStorage: KVStorage
) : ViewModel() {
fun logout() {
@ -61,7 +58,7 @@ class SettingsScreenViewModel @Inject constructor(
@Composable
fun SettingsScreen(
navController: NavController,
viewModel: SettingsScreenViewModel = hiltViewModel()
viewModel: SettingsScreenViewModel = koinViewModel()
) {
val context = LocalContext.current
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

View File

@ -1,6 +1,6 @@
package chat.stoat.screens.settings.channel
import android.content.Context
import android.app.Application
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
@ -47,7 +47,6 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
@ -57,18 +56,15 @@ import chat.stoat.api.STOAT_FILES
import chat.stoat.api.StoatAPI
import chat.stoat.api.routes.channel.patchChannel
import chat.stoat.api.routes.microservices.autumn.uploadToAutumn
import chat.stoat.core.model.schemas.Channel
import chat.stoat.composables.generic.InlineMediaPicker
import chat.stoat.composables.generic.ListHeader
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import chat.stoat.core.model.schemas.Channel
import io.ktor.http.ContentType
import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import java.io.File
import javax.inject.Inject
@HiltViewModel
class ChannelSettingsOverviewViewModel @Inject constructor(@ApplicationContext val context: Context) :
class ChannelSettingsOverviewViewModel(val context: Application) :
ViewModel() {
var initialChannel by mutableStateOf<Channel?>(null)
@ -211,7 +207,7 @@ class ChannelSettingsOverviewViewModel @Inject constructor(@ApplicationContext v
fun ChannelSettingsOverview(
navController: NavController,
channelId: String,
viewModel: ChannelSettingsOverviewViewModel = hiltViewModel()
viewModel: ChannelSettingsOverviewViewModel = koinViewModel()
) {
val currentChannel = StoatAPI.channelCache[channelId]
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

View File

@ -1,7 +1,6 @@
package chat.stoat.sheets
import android.annotation.SuppressLint
import android.content.Context
import android.app.Application
import android.util.Log
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.ExperimentalFoundationApi
@ -29,7 +28,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import chat.stoat.R
@ -46,11 +44,9 @@ import chat.stoat.composables.generic.SheetHeaderPadding
import chat.stoat.composables.generic.presenceFromStatus
import chat.stoat.core.model.schemas.Member
import chat.stoat.core.model.schemas.User
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import javax.inject.Inject
import org.koin.androidx.compose.koinViewModel
val DO_NOT_FETCH_OFFLINE_MEMBERS_SERVERS = listOf(
"01F7ZSBSFHQ8TA81725KQCSDDP" // Lounge
@ -62,10 +58,8 @@ sealed class MemberListSheetItem {
data class CategoryItem(val category: String, val count: Int) : MemberListSheetItem()
}
@HiltViewModel
@SuppressLint("StaticFieldLeak")
class MemberListSheetViewModel @Inject constructor(
@ApplicationContext private val context: Context
class MemberListSheetViewModel(
private val context: Application
) : ViewModel() {
val fullItemList = mutableStateListOf<MemberListSheetItem>()
@ -207,7 +201,7 @@ class MemberListSheetViewModel @Inject constructor(
fun MemberListSheet(
channelId: String,
serverId: String? = null,
viewModel: MemberListSheetViewModel = hiltViewModel()
viewModel: MemberListSheetViewModel = koinViewModel()
) {
var showUserInfoSheet by remember { mutableStateOf(false) }
var userInfoSheetTarget by remember { mutableStateOf("") }

View File

@ -5,7 +5,6 @@ plugins {
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.aboutlibraries) apply false
alias(libs.plugins.ksp) apply false
alias(libs.plugins.google.services) apply false

View File

@ -1,7 +1,6 @@
plugins {
alias(libs.plugins.android.library)
id("kotlin-parcelize")
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
java {

View File

@ -7,10 +7,9 @@ kotlin = "2.3.10"
kotlin-serialization = "1.10.0"
ksp = "2.3.5"
agp = "9.1.0"
hilt = "2.59.2"
google-services = "4.4.4"
sentry = "8.13.2" # This is separate from the gradle plugin version which is defined ad hoc below
sqldelight = "2.0.1"
sqldelight = "2.3.2"
compose-bom = "2026.03.00"
material3 = "1.5.0-alpha15"
lifecycle = "2.10.0"
@ -25,6 +24,7 @@ chucker = "4.3.1"
androidx-test = "1.6.1"
livekit = "2.24.1"
livekit-compose = "2.3.0"
koin = "4.2.1"
[libraries]
android-core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
@ -67,10 +67,7 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "k
ktor-client-contentnegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version = "2.9.0" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.2.0" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version = "2.9.8" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compose = { module = "com.github.bumptech.glide:compose", version = "1.0.0-beta01" }
glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
@ -101,6 +98,9 @@ square-logcat = { module = "com.squareup.logcat:logcat", version = "0.1" }
livekit-android = { module = "io.livekit:livekit-android", version.ref = "livekit" }
livekit-android-camerax = { module = "io.livekit:livekit-android-camerax", version.ref = "livekit" }
livekit-android-compose = { module = "io.livekit:livekit-android-compose-components", version.ref = "livekit-compose" }
koin = { module = "io.insert-koin:koin-android", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" }
koin-compose-viewmodel-navigation = { module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
@ -109,9 +109,8 @@ kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
sentry-android = { id = "io.sentry.android.gradle", version = "4.12.0" }
sentry-android = { id = "io.sentry.android.gradle", version = "6.5.0" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }