From 68e0b56afaebe5ed310d4d413c2f8e004d0379cd Mon Sep 17 00:00:00 2001 From: infi Date: Sun, 3 May 2026 19:43:07 +0200 Subject: [PATCH] feat: migrate from hilt to koin for DI --- app/build.gradle.kts | 32 +++++++++-------- .../main/java/chat/stoat/StoatApplication.kt | 13 +++++-- .../chat/stoat/activities/MainActivity.kt | 16 +++------ .../stoat/activities/ShareTargetActivity.kt | 19 +++++----- app/src/main/java/chat/stoat/di/AppModule.kt | 10 ++++++ .../java/chat/stoat/di/ViewModelModule.kt | 35 +++++++++++++++++++ .../java/chat/stoat/persistence/KVStorage.kt | 8 ++--- .../stoat/screens/chat/ChatRouterScreen.kt | 14 +++----- .../chat/views/channel/ChannelScreen.kt | 4 +-- .../views/channel/ChannelScreenViewModel.kt | 5 +-- .../chat/stoat/screens/login/LoginScreen.kt | 9 ++--- .../chat/stoat/screens/login/MfaScreen.kt | 9 ++--- .../settings/AppearanceSettingsScreen.kt | 23 +++++------- .../screens/settings/ChangelogsScreen.kt | 14 +++----- .../screens/settings/DebugSettingsScreen.kt | 9 ++--- .../screens/settings/ProfileSettngsScreen.kt | 15 +++----- .../stoat/screens/settings/SettingsScreen.kt | 9 ++--- .../channel/ChannelSettingsOverview.kt | 14 +++----- .../java/chat/stoat/sheets/MemberListSheet.kt | 16 +++------ build.gradle.kts | 1 - core/model/build.gradle.kts | 1 - gradle/libs.versions.toml | 15 ++++---- 22 files changed, 141 insertions(+), 150 deletions(-) create mode 100644 app/src/main/java/chat/stoat/di/AppModule.kt create mode 100644 app/src/main/java/chat/stoat/di/ViewModelModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2465a003..7fdd1059 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/chat/stoat/StoatApplication.kt b/app/src/main/java/chat/stoat/StoatApplication.kt index d48d6145..d22977c6 100644 --- a/app/src/main/java/chat/stoat/StoatApplication.kt +++ b/app/src/main/java/chat/stoat/StoatApplication.kt @@ -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. diff --git a/app/src/main/java/chat/stoat/activities/MainActivity.kt b/app/src/main/java/chat/stoat/activities/MainActivity.kt index d468fa95..864371d1 100644 --- a/app/src/main/java/chat/stoat/activities/MainActivity.kt +++ b/app/src/main/java/chat/stoat/activities/MainActivity.kt @@ -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(null) var isConnected = MutableStateFlow(false) @@ -324,9 +317,8 @@ class MainActivityViewModel @Inject constructor( } } -@AndroidEntryPoint class MainActivity : AppCompatActivity() { - private val viewModel by viewModels() + private val viewModel: MainActivityViewModel by viewModel() // Fix for SDK >=31, where core-splashscreen accidentally removes dynamic colours // See the other one in DefaultDestinationScreen.kt diff --git a/app/src/main/java/chat/stoat/activities/ShareTargetActivity.kt b/app/src/main/java/chat/stoat/activities/ShareTargetActivity.kt index a9392c25..c337ba61 100644 --- a/app/src/main/java/chat/stoat/activities/ShareTargetActivity.kt +++ b/app/src/main/java/chat/stoat/activities/ShareTargetActivity.kt @@ -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?, 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 diff --git a/app/src/main/java/chat/stoat/di/AppModule.kt b/app/src/main/java/chat/stoat/di/AppModule.kt new file mode 100644 index 00000000..07ecec19 --- /dev/null +++ b/app/src/main/java/chat/stoat/di/AppModule.kt @@ -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()) } +} \ No newline at end of file diff --git a/app/src/main/java/chat/stoat/di/ViewModelModule.kt b/app/src/main/java/chat/stoat/di/ViewModelModule.kt new file mode 100644 index 00000000..aa80571b --- /dev/null +++ b/app/src/main/java/chat/stoat/di/ViewModelModule.kt @@ -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()) } +} \ No newline at end of file diff --git a/app/src/main/java/chat/stoat/persistence/KVStorage.kt b/app/src/main/java/chat/stoat/persistence/KVStorage.kt index ca33a3b5..40812495 100644 --- a/app/src/main/java/chat/stoat/persistence/KVStorage.kt +++ b/app/src/main/java/chat/stoat/persistence/KVStorage.kt @@ -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 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 diff --git a/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt index 474c5f31..6303a380 100644 --- a/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/stoat/screens/chat/ChatRouterScreen.kt @@ -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.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() diff --git a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt index 7489c66b..f5d4feb7 100644 --- a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreen.kt @@ -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() ) { // val scope = rememberCoroutineScope() diff --git a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreenViewModel.kt b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreenViewModel.kt index 6acabddc..36ce737e 100644 --- a/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreenViewModel.kt +++ b/app/src/main/java/chat/stoat/screens/chat/views/channel/ChannelScreenViewModel.kt @@ -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() diff --git a/app/src/main/java/chat/stoat/screens/login/LoginScreen.kt b/app/src/main/java/chat/stoat/screens/login/LoginScreen.kt index 19e0f5c2..13de42bb 100644 --- a/app/src/main/java/chat/stoat/screens/login/LoginScreen.kt +++ b/app/src/main/java/chat/stoat/screens/login/LoginScreen.kt @@ -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()) diff --git a/app/src/main/java/chat/stoat/screens/login/MfaScreen.kt b/app/src/main/java/chat/stoat/screens/login/MfaScreen.kt index d39c9deb..78b4b3e3 100644 --- a/app/src/main/java/chat/stoat/screens/login/MfaScreen.kt +++ b/app/src/main/java/chat/stoat/screens/login/MfaScreen.kt @@ -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(",") diff --git a/app/src/main/java/chat/stoat/screens/settings/AppearanceSettingsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/AppearanceSettingsScreen.kt index fbaf9d76..07113834 100644 --- a/app/src/main/java/chat/stoat/screens/settings/AppearanceSettingsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/AppearanceSettingsScreen.kt @@ -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(null) var selectedOverrideInitialValue by mutableStateOf(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() diff --git a/app/src/main/java/chat/stoat/screens/settings/ChangelogsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/ChangelogsScreen.kt index 849a176d..95e502a4 100644 --- a/app/src/main/java/chat/stoat/screens/settings/ChangelogsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/ChangelogsScreen.kt @@ -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(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() diff --git a/app/src/main/java/chat/stoat/screens/settings/DebugSettingsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/DebugSettingsScreen.kt index 218d1891..67de72f2 100644 --- a/app/src/main/java/chat/stoat/screens/settings/DebugSettingsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/DebugSettingsScreen.kt @@ -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() diff --git a/app/src/main/java/chat/stoat/screens/settings/ProfileSettngsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/ProfileSettngsScreen.kt index 3c85c7a0..85512b03 100644 --- a/app/src/main/java/chat/stoat/screens/settings/ProfileSettngsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/ProfileSettngsScreen.kt @@ -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(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() diff --git a/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt b/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt index ee8fc760..2a422df4 100644 --- a/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/chat/stoat/screens/settings/SettingsScreen.kt @@ -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() diff --git a/app/src/main/java/chat/stoat/screens/settings/channel/ChannelSettingsOverview.kt b/app/src/main/java/chat/stoat/screens/settings/channel/ChannelSettingsOverview.kt index 2be972cc..37e544f8 100644 --- a/app/src/main/java/chat/stoat/screens/settings/channel/ChannelSettingsOverview.kt +++ b/app/src/main/java/chat/stoat/screens/settings/channel/ChannelSettingsOverview.kt @@ -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(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() diff --git a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt index fd3d9361..68b33cfa 100644 --- a/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/stoat/sheets/MemberListSheet.kt @@ -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() @@ -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("") } diff --git a/build.gradle.kts b/build.gradle.kts index a9e8f484..b04afce5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 7f9e78cf..a8ae76cd 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.android.library) id("kotlin-parcelize") - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.serialization) } java { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e394bf9a..1793d32c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } \ No newline at end of file