diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index e7507fdc7..7651192ae 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -73,8 +73,10 @@ android { } dependencies { - implementation(projects.themeM3.main) + implementation(projects.themeM3.main.main) + implementation(projects.themeM3.main.mainDi) implementation(projects.shared.core) + implementation(projects.shared.coreDi) implementation(libs.settings) implementation(libs.android.material) @@ -88,6 +90,9 @@ dependencies { implementation(libs.androidx.compose.navigation) implementation(libs.androidx.profile) + implementation(libs.koin.core) + implementation(libs.koin.android) + implementation(libs.coil.compose) implementation(libs.coil.svg) } diff --git a/androidApp/src/main/java/org/gdglille/devfest/android/MainActivity.kt b/androidApp/src/main/java/org/gdglille/devfest/android/MainActivity.kt index a546b7c66..79fa0c128 100644 --- a/androidApp/src/main/java/org/gdglille/devfest/android/MainActivity.kt +++ b/androidApp/src/main/java/org/gdglille/devfest/android/MainActivity.kt @@ -1,6 +1,5 @@ package org.gdglille.devfest.android -import android.app.AlarmManager import android.content.Context import android.content.Intent import android.net.Uri @@ -16,31 +15,12 @@ import androidx.core.app.ShareCompat import androidx.core.content.FileProvider import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController -import com.russhwolf.settings.AndroidSettings import com.russhwolf.settings.ExperimentalSettingsApi import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import org.gdglille.devfest.AlarmScheduler -import org.gdglille.devfest.AndroidContext -import org.gdglille.devfest.Platform -import org.gdglille.devfest.QrCodeGeneratorAndroid import org.gdglille.devfest.android.theme.Main import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.database.DatabaseWrapper -import org.gdglille.devfest.database.EventDao -import org.gdglille.devfest.database.FeaturesActivatedDao -import org.gdglille.devfest.database.PartnerDao -import org.gdglille.devfest.database.ScheduleDao -import org.gdglille.devfest.database.SpeakerDao -import org.gdglille.devfest.database.TalkDao -import org.gdglille.devfest.database.UserDao -import org.gdglille.devfest.network.ConferenceApi -import org.gdglille.devfest.repositories.AgendaRepository -import org.gdglille.devfest.repositories.EventRepository -import org.gdglille.devfest.repositories.SpeakerRepository -import org.gdglille.devfest.repositories.UserRepository import java.io.File -import java.util.Locale @Suppress("LongMethod") @FlowPreview @@ -57,46 +37,6 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() - val baseUrl = BuildConfig.BASE_URL - val db = DatabaseWrapper(context = this).createDb() - val platform = Platform(AndroidContext(this.applicationContext)) - val api = ConferenceApi.create( - platform = platform, - baseUrl = baseUrl, - acceptLanguage = Locale.getDefault().toLanguageTag(), - enableNetworkLogs = BuildConfig.DEBUG - ) - val settings = AndroidSettings( - getSharedPreferences(BuildConfig.APPLICATION_ID, MODE_PRIVATE) - ) - val eventRepository = EventRepository.Factory.create( - api = api, - eventDao = EventDao(db, settings) - ) - val agendaRepository = AgendaRepository.Factory.create( - api = api, - scheduleDao = ScheduleDao(db, settings, platform), - speakerDao = SpeakerDao(db, platform), - talkDao = TalkDao(db, platform), - eventDao = EventDao(db, settings), - partnerDao = PartnerDao(db = db, platform = platform), - featuresDao = FeaturesActivatedDao(db, settings), - qrCodeGenerator = QrCodeGeneratorAndroid() - ) - val userRepository = UserRepository.Factory.create( - userDao = UserDao(db, platform), - eventDao = EventDao(db, settings), - qrCodeGenerator = QrCodeGeneratorAndroid() - ) - val speakerRepository = SpeakerRepository.Factory.create( - speakerDao = SpeakerDao(db, platform), - eventDao = EventDao(db, settings) - ) - val scheduler = AlarmScheduler( - agendaRepository, - getSystemService(ALARM_SERVICE) as AlarmManager, - AlarmIntentFactoryImpl - ) val openfeedbackFirebaseConfig = (application as MainApplication).openFeedbackConfig setContent { val inDarkTheme = isSystemInDarkTheme() @@ -115,11 +55,6 @@ class MainActivity : ComponentActivity() { val reportSubject = stringResource(id = R.string.text_report_subject) val reportAppTarget = stringResource(id = R.string.text_report_app_target) Main( - eventRepository = eventRepository, - agendaRepository = agendaRepository, - userRepository = userRepository, - speakerRepository = speakerRepository, - alarmScheduler = scheduler, openfeedbackFirebaseConfig = openfeedbackFirebaseConfig, launchUrl = { launchUrl(it) }, onContactExportClicked = { export -> diff --git a/androidApp/src/main/java/org/gdglille/devfest/android/MainApplication.kt b/androidApp/src/main/java/org/gdglille/devfest/android/MainApplication.kt index 666982538..4b9a19e63 100644 --- a/androidApp/src/main/java/org/gdglille/devfest/android/MainApplication.kt +++ b/androidApp/src/main/java/org/gdglille/devfest/android/MainApplication.kt @@ -7,6 +7,9 @@ import coil.decode.SvgDecoder import coil.disk.DiskCache import coil.memory.MemoryCache import io.openfeedback.android.viewmodels.OpenFeedbackFirebaseConfig +import org.gdglille.devfest.android.di.appModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin private const val MemoryCacheSize = 0.25 private const val DiskCacheSize = 0.10 @@ -23,6 +26,11 @@ class MainApplication : Application(), ImageLoaderFactory { apiKey = BuildConfig.FIREBASE_API_KEY, databaseUrl = "https://${BuildConfig.FIREBASE_PROJECT_ID}.firebaseio.com" ) + + startKoin { + androidContext(this@MainApplication) + modules(appModule) + } } override fun newImageLoader(): ImageLoader = ImageLoader.Builder(this) diff --git a/androidApp/src/main/java/org/gdglille/devfest/android/di/AppModule.kt b/androidApp/src/main/java/org/gdglille/devfest/android/di/AppModule.kt new file mode 100644 index 000000000..ae054c92b --- /dev/null +++ b/androidApp/src/main/java/org/gdglille/devfest/android/di/AppModule.kt @@ -0,0 +1,20 @@ +package org.gdglille.devfest.android.di + +import android.app.AlarmManager +import androidx.activity.ComponentActivity +import org.gdglille.devfest.AlarmScheduler +import org.gdglille.devfest.android.AlarmIntentFactoryImpl +import org.gdglille.devfest.android.theme.m3.main.di.mainModule +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val appModule = module { + includes(buildConfigModule, mainModule) + single { + AlarmScheduler( + get(), + androidContext().getSystemService(ComponentActivity.ALARM_SERVICE) as AlarmManager, + AlarmIntentFactoryImpl + ) + } +} diff --git a/androidApp/src/main/java/org/gdglille/devfest/android/di/BuildConfigModule.kt b/androidApp/src/main/java/org/gdglille/devfest/android/di/BuildConfigModule.kt new file mode 100644 index 000000000..8d03fa5ac --- /dev/null +++ b/androidApp/src/main/java/org/gdglille/devfest/android/di/BuildConfigModule.kt @@ -0,0 +1,14 @@ +package org.gdglille.devfest.android.di + +import org.gdglille.devfest.ApplicationIdNamed +import org.gdglille.devfest.Conference4HallBaseUrlNamed +import org.gdglille.devfest.IsDebugNamed +import org.gdglille.devfest.android.BuildConfig +import org.koin.core.qualifier.named +import org.koin.dsl.module + +val buildConfigModule = module { + single(named(IsDebugNamed)) { BuildConfig.DEBUG } + single(named(ApplicationIdNamed)) { BuildConfig.APPLICATION_ID } + single(named(Conference4HallBaseUrlNamed)) { BuildConfig.BASE_URL } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2b6595ef..b29beb27c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ kotlinxDatetime = "0.4.1" kotlinxCollection = "0.3.6" kmpNativeCoroutines = "0.13.3" ktor = "2.3.3" +koin = "3.5.3" accompanist = "0.30.1" androidGradlePlugin = "8.2.0" androidMaterial = "1.11.0" @@ -67,6 +68,11 @@ ktor-server-conditional = { group = "io.ktor", name = "ktor-server-conditional-h ktor-serialization-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +koin-core = { group = "io.insert-koin", name = "koin-core", version.ref = "koin" } +koin-android = { group = "io.insert-koin", name = "koin-android", version.ref = "koin" } +koin-androidx-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version.ref = "koin" } +koin-androidx-workmanager = { group = "io.insert-koin", name = "koin-androidx-workmanager", version.ref = "koin" } + android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } android-material = { group = "com.google.android.material", name = "material", version.ref = "androidMaterial" } diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index b14d4027c..cda3e7cbf 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -90,7 +90,6 @@ D5C92ED3283AD00800D5CF2D /* MenusVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C92ED2283AD00800D5CF2D /* MenusVM.swift */; }; D5C92ED5283ADDC900D5CF2D /* NoFavoriteTalksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C92ED4283ADDC900D5CF2D /* NoFavoriteTalksView.swift */; }; D5CAA6512821BCC7005BB242 /* RemoteImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CAA6502821BCC7005BB242 /* RemoteImage.swift */; }; - D5CAA6532821C306005BB242 /* PartnerRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CAA6522821C306005BB242 /* PartnerRowView.swift */; }; D5D5ABF5282071D2004E2F78 /* ShareSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D5ABF4282071D2004E2F78 /* ShareSheetView.swift */; }; D5D6166C27B15C7400C59EC9 /* AgendaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D6166B27B15C7400C59EC9 /* AgendaViewModel.swift */; }; D5F324A327C6E2DE007CA4C1 /* QrCodeGeneratoriOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F324A227C6E2DE007CA4C1 /* QrCodeGeneratoriOS.swift */; }; @@ -186,7 +185,6 @@ D5C92ED2283AD00800D5CF2D /* MenusVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenusVM.swift; sourceTree = ""; }; D5C92ED4283ADDC900D5CF2D /* NoFavoriteTalksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoFavoriteTalksView.swift; sourceTree = ""; }; D5CAA6502821BCC7005BB242 /* RemoteImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteImage.swift; sourceTree = ""; }; - D5CAA6522821C306005BB242 /* PartnerRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartnerRowView.swift; sourceTree = ""; }; D5D5ABF328207025004E2F78 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; D5D5ABF4282071D2004E2F78 /* ShareSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetView.swift; sourceTree = ""; }; D5D6166B27B15C7400C59EC9 /* AgendaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaViewModel.swift; sourceTree = ""; }; @@ -503,7 +501,6 @@ isa = PBXGroup; children = ( D57028A927B550BF008EF4AD /* PartnerDividerView.swift */, - D5CAA6522821C306005BB242 /* PartnerRowView.swift */, ); path = partners; sourceTree = ""; @@ -744,7 +741,6 @@ D538BED72954F6B700F25CBE /* AlarmScheduler.swift in Sources */, D59944C02833D45A00F59C1B /* TicketQrCodeView.swift in Sources */, D53256C429219DF200EA0DE7 /* DecorativeTagView.swift in Sources */, - D5CAA6532821C306005BB242 /* PartnerRowView.swift in Sources */, D5C92ECC283ACBB000D5CF2D /* MenuItemView.swift in Sources */, D5C92EC9283AC6D400D5CF2D /* PartnersViewModel.swift in Sources */, D544C7E7295487D00038CB80 /* SpeakerAvatarBorderedView.swift in Sources */, @@ -921,6 +917,7 @@ MARKETING_VERSION = 2.0.1; OTHER_LDFLAGS = ( "$(inherited)", + "-lsqlite3", "-framework", shared, ); @@ -954,6 +951,7 @@ MARKETING_VERSION = 2.0.1; OTHER_LDFLAGS = ( "$(inherited)", + "-lsqlite3", "-framework", shared, ); diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate index f0ad13374..7ae91b414 100644 Binary files a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate and b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/GERARD.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/iosApp/iosApp/components/partners/PartnerRowView.swift b/iosApp/iosApp/components/partners/PartnerRowView.swift deleted file mode 100644 index b82095f05..000000000 --- a/iosApp/iosApp/components/partners/PartnerRowView.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// PartnerRowView.swift -// iosApp -// -// Created by GERARD on 03/05/2022. -// Copyright © 2022 orgName. All rights reserved. -// - -import SwiftUI -import shared - -struct PartnerRowView: View { - let partners: Array - let parentWidth: CGFloat - let maxItems: Int = 3 - - var body: some View { - HStack(spacing: 8) { - let maxItemsFloat = CGFloat(maxItems) - let width = (parentWidth - (8 * (maxItemsFloat - 1))) / maxItemsFloat - ForEach(partners, id: \.id) { partner in - PartnerItemNavigation( - partner: partner, - size: width - ) - } - } - } -} - -struct PartnerRowView_Previews: PreviewProvider { - static var previews: some View { - GeometryReader { geometry in - let parentWidth = geometry.size.width - PartnerRowView( - partners: [ - PartnerItemUi.companion.fake, - PartnerItemUi.companion.fake, - PartnerItemUi.companion.fake - ], - parentWidth: parentWidth - ) - } - .environmentObject(ViewModelFactory()) - } -} diff --git a/iosApp/iosApp/navigations/PartnerItemNavigation.swift b/iosApp/iosApp/navigations/PartnerItemNavigation.swift index 51ec959b2..fbe13b8c3 100644 --- a/iosApp/iosApp/navigations/PartnerItemNavigation.swift +++ b/iosApp/iosApp/navigations/PartnerItemNavigation.swift @@ -12,7 +12,6 @@ import shared struct PartnerItemNavigation: View { @EnvironmentObject var viewModelFactory: ViewModelFactory let partner: PartnerItemUi - let size: CGFloat var body: some View { NavigationLink { @@ -26,7 +25,6 @@ struct PartnerItemNavigation: View { id: partner.id ) .padding() - .frame(width: size, height: size) .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 8)) } diff --git a/iosApp/iosApp/screens/partners/Partners.swift b/iosApp/iosApp/screens/partners/Partners.swift index 5961aaa54..a627a3455 100644 --- a/iosApp/iosApp/screens/partners/Partners.swift +++ b/iosApp/iosApp/screens/partners/Partners.swift @@ -21,12 +21,11 @@ struct Partners: View { LazyVStack(spacing: 8) { ForEach(partners.groups, id: \.type) { partnerGroup in Section { - ForEach(partnerGroup.partners, id: \.[0].id) { partners in - PartnerRowView( - partners: partners, - parentWidth: parentWidth - ) - } + LazyVGrid(columns: [GridItem(.adaptive(minimum: parentWidth)), GridItem(.adaptive(minimum: parentWidth)), GridItem(.adaptive(minimum: parentWidth))], content: { + ForEach(partnerGroup.partners, id: \.id) { partner in + PartnerItemNavigation(partner: partner) + } + }) } header : { PartnerDividerView(text: partnerGroup.type) .padding(.horizontal, self.horizontalSpacing) diff --git a/settings.gradle.kts b/settings.gradle.kts index dcd74c702..9f6573af3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,27 +17,35 @@ pluginManagement { rootProject.name = "conferences4hall" include(":androidApp") include(":shared:core") +include(":shared:core-di") include(":shared:models") include(":shared:ui-models") include(":backend") include(":benchmark") -include(":theme-m3:main") +include(":theme-m3:main:main") +include(":theme-m3:main:main-di") include(":theme-m3:schedules:schedules-ui") include(":theme-m3:schedules:schedules-screens") include(":theme-m3:schedules:schedules-feature") +include(":theme-m3:schedules:schedules-di") include(":theme-m3:speakers:speakers-ui") include(":theme-m3:speakers:speakers-screens") include(":theme-m3:speakers:speakers-feature") +include(":theme-m3:speakers:speakers-di") include(":theme-m3:networking:networking-ui") include(":theme-m3:networking:networking-screens") include(":theme-m3:networking:networking-feature") +include(":theme-m3:networking:networking-di") include(":theme-m3:partners:partners-ui") include(":theme-m3:partners:partners-screens") include(":theme-m3:partners:partners-feature") +include(":theme-m3:partners:partners-di") include(":theme-m3:infos:infos-ui") include(":theme-m3:infos:infos-feature") +include(":theme-m3:infos:infos-di") include(":theme-m3:event-list:event-list-ui") include(":theme-m3:event-list:event-list-feature") +include(":theme-m3:event-list:event-list-di") include(":theme-m3:navigation") include(":theme-m3:style:networking") include(":theme-m3:style:partners") diff --git a/shared/core-di/build.gradle.kts b/shared/core-di/build.gradle.kts new file mode 100644 index 000000000..cb262d3e3 --- /dev/null +++ b/shared/core-di/build.gradle.kts @@ -0,0 +1,58 @@ +import org.gradle.internal.os.OperatingSystem + +plugins { + id("conferences4hall.multiplatform.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.shared.di" + buildFeatures { + buildConfig = true + } +} + +kotlin { + androidTarget() + + if (OperatingSystem.current().isMacOsX) { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + baseName = "shared-di" + isStatic = false + // Required https://github.com/cashapp/sqldelight/issues/1442 + linkerOpts.add("-lsqlite3") + } + } + } + + sourceSets { + val commonMain by getting { + dependencies { + api(projects.shared.core) + + implementation(libs.koin.core) + } + } + val androidMain by getting { + dependencies { + implementation(libs.koin.android) + } + } + if (OperatingSystem.current().isMacOsX) { + val iosX64Main by getting + val iosArm64Main by getting + val iosSimulatorArm64Main by getting + val iosMain by creating { + dependsOn(commonMain) + iosX64Main.dependsOn(this) + iosArm64Main.dependsOn(this) + iosSimulatorArm64Main.dependsOn(this) + } + } + } +} diff --git a/shared/core-di/src/androidMain/kotlin/org/gdglille/devfest/AndroidPlatformModule.kt b/shared/core-di/src/androidMain/kotlin/org/gdglille/devfest/AndroidPlatformModule.kt new file mode 100644 index 000000000..f0eaac27f --- /dev/null +++ b/shared/core-di/src/androidMain/kotlin/org/gdglille/devfest/AndroidPlatformModule.kt @@ -0,0 +1,30 @@ +package org.gdglille.devfest + +import androidx.activity.ComponentActivity +import com.russhwolf.settings.AndroidSettings +import com.russhwolf.settings.ExperimentalSettingsApi +import com.russhwolf.settings.ObservableSettings +import org.gdglille.devfest.database.DatabaseWrapper +import org.gdglille.devfest.db.Conferences4HallDatabase +import org.gdglille.devfest.repositories.QrCodeGenerator +import org.koin.android.ext.koin.androidApplication +import org.koin.android.ext.koin.androidContext +import org.koin.core.qualifier.named +import org.koin.dsl.module +import java.util.Locale + +@OptIn(ExperimentalSettingsApi::class) +actual val platformModule = module { + single { DatabaseWrapper(androidContext()).createDb() } + single { Platform(AndroidContext(androidApplication())) } + single { + AndroidSettings( + androidContext().getSharedPreferences( + /* name = */ "org.gdglille.devfest.android", + /* mode = */ ComponentActivity.MODE_PRIVATE + ) + ) + } + single(named(AcceptLanguageNamed)) { Locale.getDefault().toLanguageTag() } + single { QrCodeGeneratorAndroid() } +} diff --git a/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/DatabaseModule.kt b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/DatabaseModule.kt new file mode 100644 index 000000000..33f6bb4fa --- /dev/null +++ b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/DatabaseModule.kt @@ -0,0 +1,23 @@ +package org.gdglille.devfest + +import com.russhwolf.settings.ExperimentalSettingsApi +import org.gdglille.devfest.database.EventDao +import org.gdglille.devfest.database.FeaturesActivatedDao +import org.gdglille.devfest.database.PartnerDao +import org.gdglille.devfest.database.ScheduleDao +import org.gdglille.devfest.database.SpeakerDao +import org.gdglille.devfest.database.TalkDao +import org.gdglille.devfest.database.UserDao +import org.koin.dsl.module + +@OptIn(ExperimentalSettingsApi::class) +val databasesModule = module { + includes(platformModule) + single { EventDao(db = get(), settings = get()) } + single { FeaturesActivatedDao(db = get(), settings = get()) } + single { PartnerDao(db = get(), platform = get()) } + single { ScheduleDao(db = get(), settings = get(), platform = get()) } + single { SpeakerDao(db = get(), platform = get()) } + single { TalkDao(db = get(), platform = get()) } + single { UserDao(db = get(), platform = get()) } +} diff --git a/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/NetworkModule.kt b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/NetworkModule.kt new file mode 100644 index 000000000..5942fb18b --- /dev/null +++ b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/NetworkModule.kt @@ -0,0 +1,16 @@ +package org.gdglille.devfest + +import org.gdglille.devfest.network.ConferenceApi +import org.koin.core.qualifier.named +import org.koin.dsl.module + +val networksModule = module { + single { + ConferenceApi.create( + platform = get(), + baseUrl = get(named(Conference4HallBaseUrlNamed)), + acceptLanguage = get(named(AcceptLanguageNamed)), + enableNetworkLogs = get(named(IsDebugNamed)) + ) + } +} diff --git a/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/PlatformModule.kt b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/PlatformModule.kt new file mode 100644 index 000000000..70b7bfcc7 --- /dev/null +++ b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/PlatformModule.kt @@ -0,0 +1,10 @@ +package org.gdglille.devfest + +import org.koin.core.module.Module + +const val IsDebugNamed = "IsDebug" +const val ApplicationIdNamed = "ApplicationId" +const val AcceptLanguageNamed = "Accept-Language" +const val Conference4HallBaseUrlNamed = "Conference4HallBaseUrl" + +expect val platformModule: Module diff --git a/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/RepositoryModule.kt b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/RepositoryModule.kt new file mode 100644 index 000000000..c65516348 --- /dev/null +++ b/shared/core-di/src/commonMain/kotlin/org/gdglille/devfest/RepositoryModule.kt @@ -0,0 +1,28 @@ +package org.gdglille.devfest + +import org.gdglille.devfest.repositories.AgendaRepository +import org.gdglille.devfest.repositories.EventRepository +import org.gdglille.devfest.repositories.SpeakerRepository +import org.gdglille.devfest.repositories.UserRepository +import org.koin.dsl.module + +val repositoriesModule = module { + includes(databasesModule, networksModule) + single { + AgendaRepository.Factory.create( + api = get(), + scheduleDao = get(), + speakerDao = get(), + talkDao = get(), + eventDao = get(), + partnerDao = get(), + featuresDao = get(), + qrCodeGenerator = get() + ) + } + single { EventRepository.Factory.create(api = get(), eventDao = get()) } + single { SpeakerRepository.Factory.create(get(), eventDao = get()) } + single { + UserRepository.Factory.create(userDao = get(), eventDao = get(), qrCodeGenerator = get()) + } +} diff --git a/shared/core-di/src/iosMain/kotlin/org/gdglille/devfest/iOSPlatformModule.kt b/shared/core-di/src/iosMain/kotlin/org/gdglille/devfest/iOSPlatformModule.kt new file mode 100644 index 000000000..0d378f38d --- /dev/null +++ b/shared/core-di/src/iosMain/kotlin/org/gdglille/devfest/iOSPlatformModule.kt @@ -0,0 +1,22 @@ +package org.gdglille.devfest + +import com.russhwolf.settings.AppleSettings +import com.russhwolf.settings.ExperimentalSettingsApi +import com.russhwolf.settings.ObservableSettings +import org.gdglille.devfest.database.DatabaseWrapper +import org.gdglille.devfest.db.Conferences4HallDatabase +import org.gdglille.devfest.repositories.QrCodeGenerator +import org.koin.core.qualifier.named +import org.koin.dsl.module +import platform.Foundation.NSLocale +import platform.Foundation.NSUserDefaults.Companion.standardUserDefaults +import platform.Foundation.preferredLanguages + +@OptIn(ExperimentalSettingsApi::class) +actual val platformModule = module { + single { DatabaseWrapper().createDb() } + single { Platform(PlatformContext()) } + single { AppleSettings(standardUserDefaults) } + single(named(AcceptLanguageNamed)) { NSLocale.preferredLanguages.first() } + single { QrCodeGeneratoriOS() } +} diff --git a/shared/core/build.gradle.kts b/shared/core/build.gradle.kts index 58daf86b0..8281c4110 100644 --- a/shared/core/build.gradle.kts +++ b/shared/core/build.gradle.kts @@ -27,6 +27,8 @@ kotlin { export(libs.settings) export(projects.shared.models) export(projects.shared.uiModels) + // Required https://github.com/cashapp/sqldelight/issues/1442 + linkerOpts.add("-lsqlite3") } } } diff --git a/shared/core/src/iosMain/kotlin/org/gdglille/devfest/QrCodeGeneratoriOS.kt b/shared/core/src/iosMain/kotlin/org/gdglille/devfest/QrCodeGeneratoriOS.kt new file mode 100644 index 000000000..c75694cd1 --- /dev/null +++ b/shared/core/src/iosMain/kotlin/org/gdglille/devfest/QrCodeGeneratoriOS.kt @@ -0,0 +1,28 @@ +package org.gdglille.devfest + +import kotlinx.cinterop.ExperimentalForeignApi +import org.gdglille.devfest.models.ui.Image +import org.gdglille.devfest.repositories.QrCodeGenerator +import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB +import platform.CoreImage.CIContext +import platform.CoreImage.CIFilter +import platform.CoreImage.JPEGRepresentationOfImage +import platform.CoreImage.QRCodeGenerator +import platform.Foundation.setValue +import platform.UIKit.UIImage + +class QrCodeGeneratoriOS : QrCodeGenerator { + @OptIn(ExperimentalForeignApi::class) + override fun generate(text: String): Image { + val filter = CIFilter.QRCodeGenerator().apply { + setValue(text, forKey = "inputMessage") + } + val outputImg = filter.outputImage ?: return UIImage() + val nsData = CIContext().JPEGRepresentationOfImage( + outputImg, + CGColorSpaceCreateDeviceRGB(), + mapOf() + ) ?: return UIImage() + return UIImage(nsData) + } +} diff --git a/theme-m3/event-list/event-list-di/.gitignore b/theme-m3/event-list/event-list-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/event-list/event-list-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/event-list/event-list-di/build.gradle.kts b/theme-m3/event-list/event-list-di/build.gradle.kts new file mode 100644 index 000000000..3ebf27d72 --- /dev/null +++ b/theme-m3/event-list/event-list-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.events.di" +} + +dependencies { + implementation(projects.themeM3.eventList.eventListFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/event-list/event-list-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/di/EventListModule.kt b/theme-m3/event-list/event-list-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/di/EventListModule.kt new file mode 100644 index 000000000..9960a015c --- /dev/null +++ b/theme-m3/event-list/event-list-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/di/EventListModule.kt @@ -0,0 +1,11 @@ +package org.gdglille.devfest.android.theme.m3.events.di + +import org.gdglille.devfest.android.theme.m3.events.feature.EventListViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val eventListModule = module { + includes(repositoriesModule) + viewModel { EventListViewModel(get()) } +} diff --git a/theme-m3/event-list/event-list-feature/build.gradle.kts b/theme-m3/event-list/event-list-feature/build.gradle.kts index 327886d58..652e21d1a 100644 --- a/theme-m3/event-list/event-list-feature/build.gradle.kts +++ b/theme-m3/event-list/event-list-feature/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { implementation(projects.themeM3.navigation) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListVM.kt b/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListVM.kt index 813527623..6b0d175c1 100644 --- a/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListVM.kt +++ b/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListVM.kt @@ -1,24 +1,18 @@ package org.gdglille.devfest.android.theme.m3.events.feature -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.EventRepository +import org.koin.androidx.compose.koinViewModel -@OptIn(ExperimentalFoundationApi::class) @Composable fun EventListVM( - repository: EventRepository, onEventClicked: () -> Unit, modifier: Modifier = Modifier, - viewModel: EventListViewModel = viewModel( - factory = EventListViewModel.Factory.create(repository) - ) + viewModel: EventListViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListViewModel.kt b/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListViewModel.kt index e6ae84c70..318d58946 100644 --- a/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListViewModel.kt +++ b/theme-m3/event-list/event-list-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/events/feature/EventListViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.events.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -42,12 +41,4 @@ class EventListViewModel(private val repository: EventRepository) : ViewModel() fun savedEventId(eventId: String) = viewModelScope.launch { repository.saveEventId(eventId) } - - object Factory { - fun create(repository: EventRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - EventListViewModel(repository = repository) as T - } - } } diff --git a/theme-m3/infos/infos-di/.gitignore b/theme-m3/infos/infos-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/infos/infos-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/infos/infos-di/build.gradle.kts b/theme-m3/infos/infos-di/build.gradle.kts new file mode 100644 index 000000000..b79a64c1c --- /dev/null +++ b/theme-m3/infos/infos-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.infos.di" +} + +dependencies { + implementation(projects.themeM3.infos.infosFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/infos/infos-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/di/InfosModule.kt b/theme-m3/infos/infos-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/di/InfosModule.kt new file mode 100644 index 000000000..0e81af94f --- /dev/null +++ b/theme-m3/infos/infos-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/di/InfosModule.kt @@ -0,0 +1,19 @@ +package org.gdglille.devfest.android.theme.m3.infos.di + +import org.gdglille.devfest.android.theme.m3.infos.feature.CoCViewModel +import org.gdglille.devfest.android.theme.m3.infos.feature.EventViewModel +import org.gdglille.devfest.android.theme.m3.infos.feature.InfoViewModel +import org.gdglille.devfest.android.theme.m3.infos.feature.MenusViewModel +import org.gdglille.devfest.android.theme.m3.infos.feature.QAndAListViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val infosModule = module { + includes(repositoriesModule) + viewModel { CoCViewModel(get()) } + viewModel { EventViewModel(get()) } + viewModel { InfoViewModel(get(), get()) } + viewModel { MenusViewModel(get()) } + viewModel { QAndAListViewModel(get()) } +} diff --git a/theme-m3/infos/infos-feature/build.gradle.kts b/theme-m3/infos/infos-feature/build.gradle.kts index 995c44fe1..d286bd233 100644 --- a/theme-m3/infos/infos-feature/build.gradle.kts +++ b/theme-m3/infos/infos-feature/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { implementation(projects.themeM3.navigation) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCVM.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCVM.kt index 1971d6403..92fbd58a4 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCVM.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCVM.kt @@ -5,19 +5,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @Composable fun CoCVM( - agendaRepository: AgendaRepository, onReportByPhoneClicked: (String) -> Unit, onReportByEmailClicked: (String) -> Unit, modifier: Modifier = Modifier, - viewModel: CoCViewModel = viewModel( - factory = CoCViewModel.Factory.create(agendaRepository) - ) + viewModel: CoCViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCViewModel.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCViewModel.kt index 9b5c231d1..b637e911b 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCViewModel.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/CoCViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.infos.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -12,7 +11,7 @@ import org.gdglille.devfest.models.ui.CoCUi import org.gdglille.devfest.repositories.AgendaRepository sealed class CoCUiState { - object Loading : CoCUiState() + data object Loading : CoCUiState() data class Success(val coc: CoCUi) : CoCUiState() data class Failure(val throwable: Throwable) : CoCUiState() } @@ -33,11 +32,4 @@ class CoCViewModel(private val repository: AgendaRepository) : ViewModel() { } } } - - object Factory { - fun create(repository: AgendaRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = CoCViewModel(repository = repository) as T - } - } } diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/EventVM.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/EventVM.kt index eab0292ac..154b4ae4e 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/EventVM.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/EventVM.kt @@ -5,19 +5,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @Composable fun EventVM( - agendaRepository: AgendaRepository, onLinkClicked: (url: String?) -> Unit, onItineraryClicked: (lat: Double, lng: Double) -> Unit, modifier: Modifier = Modifier, - viewModel: EventViewModel = viewModel( - factory = EventViewModel.Factory.create(agendaRepository) - ) + viewModel: EventViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/InfoCompactVM.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/InfoCompactVM.kt index 1e46fc70b..a8e2d0fe9 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/InfoCompactVM.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/InfoCompactVM.kt @@ -11,19 +11,15 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.navigation.ActionIds import org.gdglille.devfest.android.theme.m3.navigation.TabActions import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.Scaffold -import org.gdglille.devfest.repositories.AgendaRepository -import org.gdglille.devfest.repositories.EventRepository +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalFoundationApi::class) @Composable fun InfoCompactVM( - agendaRepository: AgendaRepository, - eventRepository: EventRepository, onItineraryClicked: (lat: Double, lng: Double) -> Unit, onLinkClicked: (url: String?) -> Unit, onTicketScannerClicked: () -> Unit, @@ -31,19 +27,16 @@ fun InfoCompactVM( onReportByPhoneClicked: (String) -> Unit, onReportByEmailClicked: (String) -> Unit, modifier: Modifier = Modifier, - viewModel: InfoViewModel = viewModel( - factory = InfoViewModel.Factory.create(agendaRepository, eventRepository) - ) + viewModel: InfoViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() val title = stringResource(id = R.string.screen_info) when (uiState.value) { is InfoUiState.Loading -> Scaffold(title = title, modifier = modifier) { EventVM( - agendaRepository = agendaRepository, - modifier = Modifier.fillMaxSize(), onLinkClicked = onLinkClicked, - onItineraryClicked = onItineraryClicked + onItineraryClicked = onItineraryClicked, + modifier = Modifier.fillMaxSize() ) } @@ -85,28 +78,24 @@ fun InfoCompactVM( ) { page -> when (uiModel.tabActionsUi.actions[page].route) { TabActions.event.route -> EventVM( - agendaRepository = agendaRepository, - modifier = Modifier.fillMaxSize(), onLinkClicked = onLinkClicked, - onItineraryClicked = onItineraryClicked + onItineraryClicked = onItineraryClicked, + modifier = Modifier.fillMaxSize() ) TabActions.menus.route -> MenusVM( - agendaRepository = agendaRepository, modifier = Modifier.fillMaxSize() ) TabActions.qanda.route -> QAndAListVM( - agendaRepository = agendaRepository, - modifier = Modifier.fillMaxSize(), - onLinkClicked = onLinkClicked + onLinkClicked = onLinkClicked, + modifier = Modifier.fillMaxSize() ) TabActions.coc.route -> CoCVM( - agendaRepository = agendaRepository, - modifier = Modifier.fillMaxSize(), onReportByPhoneClicked = onReportByPhoneClicked, - onReportByEmailClicked = onReportByEmailClicked + onReportByEmailClicked = onReportByEmailClicked, + modifier = Modifier.fillMaxSize() ) else -> TODO("Screen not implemented") diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/MenusVM.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/MenusVM.kt index ceafddad8..0bbc1e395 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/MenusVM.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/MenusVM.kt @@ -6,18 +6,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun MenusVM( - agendaRepository: AgendaRepository, modifier: Modifier = Modifier, - viewModel: MenusViewModel = viewModel( - factory = MenusViewModel.Factory.create(agendaRepository) - ) + viewModel: MenusViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/QAndAListVM.kt b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/QAndAListVM.kt index b064b9a04..4aeadfb7c 100644 --- a/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/QAndAListVM.kt +++ b/theme-m3/infos/infos-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/infos/feature/QAndAListVM.kt @@ -5,18 +5,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @Composable fun QAndAListVM( - agendaRepository: AgendaRepository, onLinkClicked: (url: String) -> Unit, modifier: Modifier = Modifier, - viewModel: QAndAListViewModel = viewModel( - factory = QAndAListViewModel.Factory.create(agendaRepository) - ) + viewModel: QAndAListViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/main/main-di/.gitignore b/theme-m3/main/main-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/main/main-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/main/main-di/build.gradle.kts b/theme-m3/main/main-di/build.gradle.kts new file mode 100644 index 000000000..15a09d4b9 --- /dev/null +++ b/theme-m3/main/main-di/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.main.di" +} + +dependencies { + implementation(projects.themeM3.main.main) + implementation(projects.themeM3.eventList.eventListDi) + implementation(projects.themeM3.infos.infosDi) + implementation(projects.themeM3.networking.networkingDi) + implementation(projects.themeM3.partners.partnersDi) + implementation(projects.themeM3.schedules.schedulesDi) + implementation(projects.themeM3.speakers.speakersDi) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/main/main-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/di/MainModule.kt b/theme-m3/main/main-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/di/MainModule.kt new file mode 100644 index 000000000..7737edc98 --- /dev/null +++ b/theme-m3/main/main-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/main/di/MainModule.kt @@ -0,0 +1,26 @@ +package org.gdglille.devfest.android.theme.m3.main.di + +import org.gdglille.devfest.android.theme.MainNavigationViewModel +import org.gdglille.devfest.android.theme.MainViewModel +import org.gdglille.devfest.android.theme.m3.events.di.eventListModule +import org.gdglille.devfest.android.theme.m3.infos.di.infosModule +import org.gdglille.devfest.android.theme.m3.networking.di.networkingModule +import org.gdglille.devfest.android.theme.m3.partners.di.partnersModule +import org.gdglille.devfest.android.theme.m3.schedules.di.scheduleModule +import org.gdglille.devfest.android.theme.m3.speakers.di.speakersModule +import org.gdglille.devfest.repositoriesModule +import org.koin.dsl.module + +val mainModule = module { + includes( + repositoriesModule, + eventListModule, + infosModule, + networkingModule, + partnersModule, + scheduleModule, + speakersModule + ) + single { MainNavigationViewModel(get(), get()) } + single { MainViewModel(get()) } +} diff --git a/theme-m3/main/build.gradle.kts b/theme-m3/main/main/build.gradle.kts similarity index 97% rename from theme-m3/main/build.gradle.kts rename to theme-m3/main/main/build.gradle.kts index 8dfec9578..757cfef5f 100644 --- a/theme-m3/main/build.gradle.kts +++ b/theme-m3/main/main/build.gradle.kts @@ -22,6 +22,8 @@ dependencies { implementation(projects.shared.uiModels) implementation(projects.shared.core) + implementation(libs.koin.androidx.compose) + implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3.windowsizeclass) diff --git a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt similarity index 64% rename from theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt rename to theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt index 105590fc4..213e8fef8 100644 --- a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt +++ b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/Main.kt @@ -2,29 +2,19 @@ package org.gdglille.devfest.android.theme import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import io.openfeedback.android.viewmodels.OpenFeedbackFirebaseConfig import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import org.gdglille.devfest.AlarmScheduler import org.gdglille.devfest.android.theme.m3.style.Conferences4HallTheme import org.gdglille.devfest.models.ui.ExportNetworkingUi -import org.gdglille.devfest.repositories.AgendaRepository -import org.gdglille.devfest.repositories.EventRepository -import org.gdglille.devfest.repositories.SpeakerRepository -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @Suppress("LongMethod", "UnusedPrivateMember") @ExperimentalCoroutinesApi @FlowPreview @Composable fun Main( - eventRepository: EventRepository, - agendaRepository: AgendaRepository, - userRepository: UserRepository, - speakerRepository: SpeakerRepository, - alarmScheduler: AlarmScheduler, openfeedbackFirebaseConfig: OpenFeedbackFirebaseConfig, launchUrl: (String) -> Unit, onContactExportClicked: (ExportNetworkingUi) -> Unit, @@ -33,9 +23,7 @@ fun Main( onShareClicked: (text: String) -> Unit, onItineraryClicked: (lat: Double, lng: Double) -> Unit, navController: NavHostController, - viewModel: MainViewModel = viewModel( - factory = MainViewModel.Factory.create(eventRepository) - ) + viewModel: MainViewModel = koinViewModel() ) { Conferences4HallTheme { val uiState = viewModel.uiState.collectAsState() @@ -50,13 +38,8 @@ fun Main( onReportByEmailClicked = onReportByEmailClicked, onShareClicked = onShareClicked, onItineraryClicked = onItineraryClicked, - agendaRepository = agendaRepository, - userRepository = userRepository, - speakerRepository = speakerRepository, - eventRepository = eventRepository, - alarmScheduler = alarmScheduler, - navController = navController, - savedStateHandle = navController.currentBackStackEntry?.savedStateHandle + savedStateHandle = navController.currentBackStackEntry?.savedStateHandle, + navController = navController ) } diff --git a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt similarity index 88% rename from theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt rename to theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt index c27656d13..5981d06e1 100644 --- a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt +++ b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigation.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.NavType @@ -25,7 +24,6 @@ import androidx.navigation.navDeepLink import io.openfeedback.android.viewmodels.OpenFeedbackFirebaseConfig import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import org.gdglille.devfest.AlarmScheduler import org.gdglille.devfest.android.theme.m3.events.feature.EventListVM import org.gdglille.devfest.android.theme.m3.infos.feature.InfoCompactVM import org.gdglille.devfest.android.theme.m3.infos.feature.TicketQrCodeScanner @@ -44,10 +42,7 @@ import org.gdglille.devfest.android.theme.m3.style.appbars.iconColor import org.gdglille.devfest.models.ui.ExportNetworkingUi import org.gdglille.devfest.models.ui.VCardModel import org.gdglille.devfest.models.ui.convertToModelUi -import org.gdglille.devfest.repositories.AgendaRepository -import org.gdglille.devfest.repositories.EventRepository -import org.gdglille.devfest.repositories.SpeakerRepository -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @Suppress("LongMethod") @OptIn( @@ -64,17 +59,10 @@ fun MainNavigation( onReportByEmailClicked: (String) -> Unit, onShareClicked: (text: String) -> Unit, onItineraryClicked: (lat: Double, lng: Double) -> Unit, - agendaRepository: AgendaRepository, - userRepository: UserRepository, - speakerRepository: SpeakerRepository, - eventRepository: EventRepository, - alarmScheduler: AlarmScheduler, modifier: Modifier = Modifier, savedStateHandle: SavedStateHandle? = null, navController: NavHostController = rememberNavController(), - viewModel: MainNavigationViewModel = viewModel( - factory = MainNavigationViewModel.Factory.create(agendaRepository, userRepository) - ) + viewModel: MainNavigationViewModel = koinViewModel() ) { if (savedStateHandle != null) { val qrCodeTicket by savedStateHandle.getLiveData(ResultKey.QR_CODE_TICKET) @@ -140,7 +128,6 @@ fun MainNavigation( builder = { composable(route = Screen.EventList.route) { EventListVM( - repository = eventRepository, onEventClicked = { navController.navigate(Screen.ScheduleList.route) { this.popUpTo(Screen.EventList.route) { @@ -152,15 +139,12 @@ fun MainNavigation( } composable(Screen.ScheduleList.route) { ScheduleListCompactVM( - agendaRepository = agendaRepository, - alarmScheduler = alarmScheduler, onFilterClicked = { navController.navigate(Screen.ScheduleFilters.route) }, onTalkClicked = { navController.navigate(Screen.Schedule.route(it)) } ) } composable(route = Screen.ScheduleFilters.route) { AgendaFiltersVM( - agendaRepository = agendaRepository, onBackClicked = { navController.popBackStack() } ) } @@ -171,7 +155,6 @@ fun MainNavigation( ScheduleDetailOrientableVM( scheduleId = it.arguments?.getString("scheduleId")!!, openfeedbackFirebaseConfig = openfeedbackFirebaseConfig, - agendaRepository = agendaRepository, onBackClicked = { navController.popBackStack() }, onSpeakerClicked = { navController.navigate(Screen.Speaker.route(it)) }, onShareClicked = onShareClicked @@ -179,7 +162,6 @@ fun MainNavigation( } composable(Screen.SpeakerList.route) { SpeakersListCompactVM( - speakerRepository = speakerRepository, onSpeakerClicked = { navController.navigate(Screen.Speaker.route(it)) } ) } @@ -189,8 +171,6 @@ fun MainNavigation( ) { SpeakerDetailOrientableVM( speakerId = it.arguments?.getString("speakerId")!!, - agendaRepository = agendaRepository, - alarmScheduler = alarmScheduler, onTalkClicked = { navController.navigate(Screen.Schedule.route(it)) }, onLinkClicked = { launchUrl(it) }, onBackClicked = { navController.popBackStack() } @@ -198,8 +178,6 @@ fun MainNavigation( } composable(Screen.MyProfile.route) { NetworkingCompactVM( - agendaRepository = agendaRepository, - userRepository = userRepository, onCreateProfileClicked = { navController.navigate(Screen.NewProfile.route) }, onContactScannerClicked = { navController.navigate(Screen.ScannerVCard.route) }, onContactExportClicked = onContactExportClicked @@ -207,13 +185,11 @@ fun MainNavigation( } composable(route = Screen.NewProfile.route) { ProfileInputVM( - userRepository = userRepository, onBackClicked = { navController.popBackStack() } ) } composable(Screen.PartnerList.route) { PartnersListCompactVM( - agendaRepository = agendaRepository, onPartnerClick = { navController.navigate(Screen.Partner.route(it)) } ) } @@ -226,7 +202,6 @@ fun MainNavigation( ) { PartnerDetailOrientableVM( partnerId = it.arguments?.getString("partnerId")!!, - agendaRepository = agendaRepository, onLinkClicked = { launchUrl(it) }, onItineraryClicked = onItineraryClicked, onBackClicked = { navController.popBackStack() } @@ -234,8 +209,6 @@ fun MainNavigation( } composable(Screen.Event.route) { InfoCompactVM( - agendaRepository = agendaRepository, - eventRepository = eventRepository, onItineraryClicked = onItineraryClicked, onLinkClicked = { url -> url?.let { launchUrl(it) } }, onTicketScannerClicked = { navController.navigate(Screen.ScannerTicket.route) }, diff --git a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigationViewModel.kt b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigationViewModel.kt similarity index 100% rename from theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigationViewModel.kt rename to theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainNavigationViewModel.kt diff --git a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainViewModel.kt b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainViewModel.kt similarity index 100% rename from theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainViewModel.kt rename to theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/MainViewModel.kt diff --git a/theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/ResultKey.kt b/theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/ResultKey.kt similarity index 100% rename from theme-m3/main/src/main/kotlin/org/gdglille/devfest/android/theme/ResultKey.kt rename to theme-m3/main/main/src/main/kotlin/org/gdglille/devfest/android/theme/ResultKey.kt diff --git a/theme-m3/main/src/main/AndroidManifest.xml b/theme-m3/main/src/main/AndroidManifest.xml deleted file mode 100644 index 8072ee00d..000000000 --- a/theme-m3/main/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/theme-m3/networking/networking-di/.gitignore b/theme-m3/networking/networking-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/networking/networking-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/networking/networking-di/build.gradle.kts b/theme-m3/networking/networking-di/build.gradle.kts new file mode 100644 index 000000000..fd83c38e7 --- /dev/null +++ b/theme-m3/networking/networking-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.networking.di" +} + +dependencies { + implementation(projects.themeM3.networking.networkingFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/networking/networking-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/di/NetworkingModule.kt b/theme-m3/networking/networking-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/di/NetworkingModule.kt new file mode 100644 index 000000000..b7a43887c --- /dev/null +++ b/theme-m3/networking/networking-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/di/NetworkingModule.kt @@ -0,0 +1,17 @@ +package org.gdglille.devfest.android.theme.m3.networking.di + +import org.gdglille.devfest.android.theme.m3.networking.feature.ContactsViewModel +import org.gdglille.devfest.android.theme.m3.networking.feature.MyProfileViewModel +import org.gdglille.devfest.android.theme.m3.networking.feature.NetworkingViewModel +import org.gdglille.devfest.android.theme.m3.networking.feature.ProfileInputViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val networkingModule = module { + includes(repositoriesModule) + viewModel { ContactsViewModel(get()) } + viewModel { MyProfileViewModel(get()) } + viewModel { NetworkingViewModel(get(), get()) } + viewModel { ProfileInputViewModel(get()) } +} diff --git a/theme-m3/networking/networking-feature/build.gradle.kts b/theme-m3/networking/networking-feature/build.gradle.kts index 9e1efedd0..c5cb7ab91 100644 --- a/theme-m3/networking/networking-feature/build.gradle.kts +++ b/theme-m3/networking/networking-feature/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { implementation(projects.themeM3.style.networking) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsCompactVM.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsCompactVM.kt index 2cebafc51..5ca3317cf 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsCompactVM.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsCompactVM.kt @@ -5,18 +5,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.networking.screens.ContactsScreen import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @Composable fun ContactsCompactVM( - userRepository: UserRepository, modifier: Modifier = Modifier, - viewModel: ContactsViewModel = viewModel( - factory = ContactsViewModel.Factory.create(userRepository) - ) + viewModel: ContactsViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsViewModel.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsViewModel.kt index f4ed7e4e4..8c9e36329 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsViewModel.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ContactsViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.networking.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -40,12 +39,4 @@ class ContactsViewModel( fun deleteNetworking(email: String) = viewModelScope.launch { userRepository.deleteNetworkProfile(email) } - - object Factory { - fun create(repository: UserRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - ContactsViewModel(userRepository = repository) as T - } - } } diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileCompactVM.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileCompactVM.kt index 75ea2c65a..dbad3870a 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileCompactVM.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileCompactVM.kt @@ -5,20 +5,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.networking.screens.EmptyNetworkingScreen import org.gdglille.devfest.android.theme.m3.networking.screens.MyProfileScreen import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @Composable fun MyProfileCompactVM( - userRepository: UserRepository, onEditInformation: () -> Unit, modifier: Modifier = Modifier, - viewModel: MyProfileViewModel = viewModel( - factory = MyProfileViewModel.Factory.create(userRepository) - ) + viewModel: MyProfileViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileViewModel.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileViewModel.kt index 0368ba813..56f1a35db 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileViewModel.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/MyProfileViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.networking.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -19,9 +18,7 @@ sealed class MyProfileUiState { data class Failure(val throwable: Throwable) : MyProfileUiState() } -class MyProfileViewModel( - userRepository: UserRepository -) : ViewModel() { +class MyProfileViewModel(userRepository: UserRepository) : ViewModel() { val uiState: StateFlow = userRepository.fetchProfile() .map { MyProfileUiState.Success( @@ -43,12 +40,4 @@ class MyProfileViewModel( initialValue = MyProfileUiState.Loading, started = SharingStarted.WhileSubscribed() ) - - object Factory { - fun create(repository: UserRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - MyProfileViewModel(userRepository = repository) as T - } - } } diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingCompactVM.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingCompactVM.kt index 74c00199c..aad4bf341 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingCompactVM.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingCompactVM.kt @@ -11,28 +11,22 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.navigation.ActionIds import org.gdglille.devfest.android.theme.m3.navigation.TabActions import org.gdglille.devfest.android.theme.m3.networking.screens.EmptyNetworkingScreen import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.Scaffold import org.gdglille.devfest.models.ui.ExportNetworkingUi -import org.gdglille.devfest.repositories.AgendaRepository -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalFoundationApi::class) @Composable fun NetworkingCompactVM( - agendaRepository: AgendaRepository, - userRepository: UserRepository, onCreateProfileClicked: () -> Unit, onContactScannerClicked: () -> Unit, onContactExportClicked: (ExportNetworkingUi) -> Unit, modifier: Modifier = Modifier, - viewModel: NetworkingViewModel = viewModel( - factory = NetworkingViewModel.Factory.create(agendaRepository, userRepository) - ) + viewModel: NetworkingViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() val exportPath = viewModel.exportPath.collectAsState(null) @@ -85,11 +79,10 @@ fun NetworkingCompactVM( ) { page -> when (uiModel.tabActionsUi.actions[page].route) { TabActions.myProfile.route -> MyProfileCompactVM( - userRepository = userRepository, onEditInformation = onCreateProfileClicked ) - TabActions.contacts.route -> ContactsCompactVM(userRepository = userRepository) + TabActions.contacts.route -> ContactsCompactVM() else -> TODO("Screen not implemented") } diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingViewModel.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingViewModel.kt index 7666dd5ca..ace09d05c 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingViewModel.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/NetworkingViewModel.kt @@ -85,16 +85,4 @@ class NetworkingViewModel( fun exportNetworking() = viewModelScope.launch { _exportPath.tryEmit(userRepository.exportNetworking()) } - - object Factory { - fun create(agendaRepository: AgendaRepository, userRepository: UserRepository) = - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - NetworkingViewModel( - agendaRepository = agendaRepository, - userRepository = userRepository - ) as T - } - } } diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputVM.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputVM.kt index 476173a71..88eb79b52 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputVM.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputVM.kt @@ -6,20 +6,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.networking.screens.ProfileInputScreen import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.UserRepository +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileInputVM( - userRepository: UserRepository, onBackClicked: () -> Unit, modifier: Modifier = Modifier, - viewModel: ProfileInputViewModel = viewModel( - factory = ProfileInputViewModel.Factory.create(userRepository) - ) + viewModel: ProfileInputViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputViewModel.kt b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputViewModel.kt index d223feac3..76f017376 100644 --- a/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputViewModel.kt +++ b/theme-m3/networking/networking-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/networking/feature/ProfileInputViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.networking.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -13,7 +12,7 @@ import org.gdglille.devfest.models.ui.UserProfileUi import org.gdglille.devfest.repositories.UserRepository sealed class ProfileInputUiState { - object Loading : ProfileInputUiState() + data object Loading : ProfileInputUiState() data class Success(val profile: UserProfileUi) : ProfileInputUiState() data class Failure(val throwable: Throwable) : ProfileInputUiState() } @@ -69,12 +68,4 @@ class ProfileInputViewModel( profile.company ) } - - object Factory { - fun create(repository: UserRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - ProfileInputViewModel(userRepository = repository) as T - } - } } diff --git a/theme-m3/partners/partners-di/.gitignore b/theme-m3/partners/partners-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/partners/partners-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/partners/partners-di/build.gradle.kts b/theme-m3/partners/partners-di/build.gradle.kts new file mode 100644 index 000000000..949a170c1 --- /dev/null +++ b/theme-m3/partners/partners-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.partners.di" +} + +dependencies { + implementation(projects.themeM3.partners.partnersFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/partners/partners-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/di/PartnersModule.kt b/theme-m3/partners/partners-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/di/PartnersModule.kt new file mode 100644 index 000000000..1763bf71d --- /dev/null +++ b/theme-m3/partners/partners-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/di/PartnersModule.kt @@ -0,0 +1,12 @@ +package org.gdglille.devfest.android.theme.m3.partners.di + +import org.gdglille.devfest.android.theme.m3.partners.feature.PartnerDetailViewModel +import org.gdglille.devfest.android.theme.m3.partners.feature.PartnersViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.dsl.module + +val partnersModule = module { + includes(repositoriesModule) + single { PartnersViewModel(get()) } + single { parametersHolder -> PartnerDetailViewModel(parametersHolder.get(), get()) } +} diff --git a/theme-m3/partners/partners-feature/build.gradle.kts b/theme-m3/partners/partners-feature/build.gradle.kts index 340266c9e..f0fbbb1b9 100644 --- a/theme-m3/partners/partners-feature/build.gradle.kts +++ b/theme-m3/partners/partners-feature/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { implementation(projects.themeM3.style.partners) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailOrientableVM.kt b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailOrientableVM.kt index 3206132f5..cfc73c653 100644 --- a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailOrientableVM.kt +++ b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailOrientableVM.kt @@ -10,24 +10,21 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.partners.screens.PartnerDetailOrientable import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.appbars.TopAppBar -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @OptIn(ExperimentalMaterial3Api::class) @Composable fun PartnerDetailOrientableVM( partnerId: String, - agendaRepository: AgendaRepository, onLinkClicked: (url: String) -> Unit, onItineraryClicked: (lat: Double, lng: Double) -> Unit, onBackClicked: () -> Unit, modifier: Modifier = Modifier, - viewModel: PartnerDetailViewModel = viewModel( - factory = PartnerDetailViewModel.Factory.create(partnerId, agendaRepository) - ) + viewModel: PartnerDetailViewModel = koinViewModel(parameters = { parametersOf(partnerId) }) ) { val uiState = viewModel.uiState.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) diff --git a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailViewModel.kt b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailViewModel.kt index 62420fb98..b922c272f 100644 --- a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailViewModel.kt +++ b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnerDetailViewModel.kt @@ -1,13 +1,14 @@ package org.gdglille.devfest.android.theme.m3.partners.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import org.gdglille.devfest.models.ui.PartnerItemUi import org.gdglille.devfest.repositories.AgendaRepository @@ -17,34 +18,16 @@ sealed class PartnerUiState { data class Failure(val throwable: Throwable) : PartnerUiState() } -class PartnerDetailViewModel( - private val partnerId: String, - private val repository: AgendaRepository, -) : ViewModel() { - private val _uiState = - MutableStateFlow(PartnerUiState.Loading(PartnerItemUi.fake)) - val uiState: StateFlow = _uiState - - init { - viewModelScope.launch { - try { - repository.partner(partnerId).collect { - _uiState.value = PartnerUiState.Success(it) - } - } catch (error: Throwable) { - Firebase.crashlytics.recordException(error) - _uiState.value = PartnerUiState.Failure(error) - } +class PartnerDetailViewModel(partnerId: String, repository: AgendaRepository) : ViewModel() { + val uiState: StateFlow = repository.partner(partnerId) + .map { PartnerUiState.Success(it) } + .catch { + Firebase.crashlytics.recordException(it) + PartnerUiState.Failure(it) } - } - - object Factory { - fun create(partnerId: String, repository: AgendaRepository) = - object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") - return PartnerDetailViewModel(partnerId, repository) as T - } - } - } + .stateIn( + scope = viewModelScope, + initialValue = PartnerUiState.Loading(PartnerItemUi.fake), + started = SharingStarted.WhileSubscribed() + ) } diff --git a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersListCompactVM.kt b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersListCompactVM.kt index 710f65018..8f843463e 100644 --- a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersListCompactVM.kt +++ b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersListCompactVM.kt @@ -10,11 +10,10 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.partners.screens.PartnersListScreen import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.Scaffold -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel private const val ColumnCountLandscape = 6 private const val ColumnCountPortrait = 3 @@ -22,12 +21,9 @@ private const val ColumnCountPortrait = 3 @OptIn(ExperimentalFoundationApi::class) @Composable fun PartnersListCompactVM( - agendaRepository: AgendaRepository, onPartnerClick: (id: String) -> Unit, modifier: Modifier = Modifier, - viewModel: PartnersViewModel = viewModel( - factory = PartnersViewModel.Factory.create(agendaRepository) - ) + viewModel: PartnersViewModel = koinViewModel() ) { val configuration = LocalConfiguration.current val state = rememberLazyGridState() diff --git a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersViewModel.kt b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersViewModel.kt index 1488f50af..49113851b 100644 --- a/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersViewModel.kt +++ b/theme-m3/partners/partners-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/partners/feature/PartnersViewModel.kt @@ -1,13 +1,14 @@ package org.gdglille.devfest.android.theme.m3.partners.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import org.gdglille.devfest.models.ui.PartnerGroupsUi import org.gdglille.devfest.repositories.AgendaRepository @@ -17,29 +18,16 @@ sealed class PartnersUiState { data class Failure(val throwable: Throwable) : PartnersUiState() } -class PartnersViewModel(private val repository: AgendaRepository) : ViewModel() { - private val _uiState = - MutableStateFlow(PartnersUiState.Loading(PartnerGroupsUi.fake)) - val uiState: StateFlow = _uiState - - init { - viewModelScope.launch { - try { - repository.partners().collect { - _uiState.value = PartnersUiState.Success(it) - } - } catch (error: Throwable) { - Firebase.crashlytics.recordException(error) - _uiState.value = PartnersUiState.Failure(error) - } - } - } - - object Factory { - fun create(repository: AgendaRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - PartnersViewModel(repository = repository) as T +class PartnersViewModel(repository: AgendaRepository) : ViewModel() { + val uiState: StateFlow = repository.partners() + .map { PartnersUiState.Success(it) } + .catch { + Firebase.crashlytics.recordException(it) + PartnersUiState.Failure(it) } - } + .stateIn( + scope = viewModelScope, + initialValue = PartnersUiState.Loading(PartnerGroupsUi.fake), + started = SharingStarted.WhileSubscribed() + ) } diff --git a/theme-m3/schedules/schedules-di/.gitignore b/theme-m3/schedules/schedules-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/schedules/schedules-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/schedules/schedules-di/build.gradle.kts b/theme-m3/schedules/schedules-di/build.gradle.kts new file mode 100644 index 000000000..a72186871 --- /dev/null +++ b/theme-m3/schedules/schedules-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.schedules.di" +} + +dependencies { + implementation(projects.themeM3.schedules.schedulesFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/schedules/schedules-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/di/SchedulesModule.kt b/theme-m3/schedules/schedules-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/di/SchedulesModule.kt new file mode 100644 index 000000000..cefbb9f80 --- /dev/null +++ b/theme-m3/schedules/schedules-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/di/SchedulesModule.kt @@ -0,0 +1,18 @@ +package org.gdglille.devfest.android.theme.m3.schedules.di + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview +import org.gdglille.devfest.android.theme.m3.schedules.feature.AgendaFiltersViewModel +import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleDetailViewModel +import org.gdglille.devfest.android.theme.m3.schedules.feature.ScheduleListViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) +val scheduleModule = module { + includes(repositoriesModule) + viewModel { AgendaFiltersViewModel(get()) } + viewModel { parameters -> ScheduleDetailViewModel(parameters.get(), get()) } + viewModel { ScheduleListViewModel(get(), get()) } +} diff --git a/theme-m3/schedules/schedules-feature/build.gradle.kts b/theme-m3/schedules/schedules-feature/build.gradle.kts index 5b7b330e4..a591e7486 100644 --- a/theme-m3/schedules/schedules-feature/build.gradle.kts +++ b/theme-m3/schedules/schedules-feature/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { implementation(projects.themeM3.style.schedules) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersVM.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersVM.kt index dc488013e..634d75839 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersVM.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersVM.kt @@ -5,19 +5,15 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.schedules.screens.AgendaFiltersScreen import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @Composable fun AgendaFiltersVM( - agendaRepository: AgendaRepository, onBackClicked: () -> Unit, modifier: Modifier = Modifier, - viewModel: AgendaFiltersViewModel = viewModel( - factory = AgendaFiltersViewModel.Factory.create(agendaRepository) - ) + viewModel: AgendaFiltersViewModel = koinViewModel() ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersViewModel.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersViewModel.kt index c11d16e4c..ec1425927 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersViewModel.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/AgendaFiltersViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.schedules.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -14,7 +13,7 @@ import org.gdglille.devfest.models.ui.FormatUi import org.gdglille.devfest.repositories.AgendaRepository sealed class AgendaFiltersUiState { - object Loading : AgendaFiltersUiState() + data object Loading : AgendaFiltersUiState() data class Success(val filters: FiltersUi) : AgendaFiltersUiState() data class Failure(val throwable: Throwable) : AgendaFiltersUiState() } @@ -22,8 +21,7 @@ sealed class AgendaFiltersUiState { class AgendaFiltersViewModel( private val repository: AgendaRepository ) : ViewModel() { - val uiState: StateFlow = repository - .filters() + val uiState: StateFlow = repository.filters() .map { result -> AgendaFiltersUiState.Success(result) } .stateIn( scope = viewModelScope, @@ -42,12 +40,4 @@ class AgendaFiltersViewModel( fun applyFormatFilter(formatUi: FormatUi, selected: Boolean) = viewModelScope.launch { repository.applyFormatFilter(formatUi, selected) } - - object Factory { - fun create(repository: AgendaRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - AgendaFiltersViewModel(repository = repository) as T - } - } } diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailOrientableVM.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailOrientableVM.kt index be6231324..b6c766bb5 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailOrientableVM.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailOrientableVM.kt @@ -6,25 +6,22 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import io.openfeedback.android.viewmodels.OpenFeedbackFirebaseConfig import org.gdglille.devfest.android.theme.m3.schedules.screens.ScheduleDetailOrientableScreen import org.gdglille.devfest.android.theme.m3.style.R -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @OptIn(ExperimentalMaterial3Api::class) @Composable fun ScheduleDetailOrientableVM( scheduleId: String, openfeedbackFirebaseConfig: OpenFeedbackFirebaseConfig, - agendaRepository: AgendaRepository, onBackClicked: () -> Unit, onSpeakerClicked: (id: String) -> Unit, onShareClicked: (text: String) -> Unit, modifier: Modifier = Modifier, - viewModel: ScheduleDetailViewModel = viewModel( - factory = ScheduleDetailViewModel.Factory.create(scheduleId, agendaRepository) - ) + viewModel: ScheduleDetailViewModel = koinViewModel(parameters = { parametersOf(scheduleId) }) ) { val uiState = viewModel.uiState.collectAsState() when (uiState.value) { diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailViewModel.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailViewModel.kt index 865cac29b..025979eaf 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailViewModel.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleDetailViewModel.kt @@ -1,7 +1,6 @@ package org.gdglille.devfest.android.theme.m3.schedules.feature import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase @@ -12,7 +11,7 @@ import org.gdglille.devfest.models.ui.TalkUi import org.gdglille.devfest.repositories.AgendaRepository sealed class ScheduleUiState { - object Loading : ScheduleUiState() + data object Loading : ScheduleUiState() data class Success(val talk: TalkUi) : ScheduleUiState() data class Failure(val throwable: Throwable) : ScheduleUiState() } @@ -34,15 +33,4 @@ class ScheduleDetailViewModel( } } } - - object Factory { - fun create(scheduleId: String, repository: AgendaRepository) = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") return ScheduleDetailViewModel( - scheduleId = scheduleId, - repository = repository - ) as T - } - } - } } diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListCompactVM.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListCompactVM.kt index c6b5535f1..d8f9d6ad6 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListCompactVM.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListCompactVM.kt @@ -11,30 +11,24 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview -import org.gdglille.devfest.AlarmScheduler import org.gdglille.devfest.android.theme.m3.navigation.ActionIds import org.gdglille.devfest.android.theme.m3.schedules.screens.ScheduleListHorizontalPager import org.gdglille.devfest.android.theme.m3.schedules.screens.ScheduleListVerticalPager import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.Scaffold -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalFoundationApi::class) @ExperimentalCoroutinesApi @FlowPreview @Composable fun ScheduleListCompactVM( - agendaRepository: AgendaRepository, - alarmScheduler: AlarmScheduler, onFilterClicked: () -> Unit, onTalkClicked: (id: String) -> Unit, modifier: Modifier = Modifier, - viewModel: ScheduleListViewModel = viewModel( - factory = ScheduleListViewModel.Factory.create(agendaRepository, alarmScheduler) - ) + viewModel: ScheduleListViewModel = koinViewModel() ) { val context = LocalContext.current val configuration = LocalConfiguration.current diff --git a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListViewModel.kt b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListViewModel.kt index 54a08f0d4..dcece2c1b 100644 --- a/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListViewModel.kt +++ b/theme-m3/schedules/schedules-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/schedules/feature/ScheduleListViewModel.kt @@ -49,7 +49,7 @@ sealed class ScheduleListUiState { @FlowPreview @ExperimentalCoroutinesApi class ScheduleListViewModel( - private val repository: AgendaRepository, + repository: AgendaRepository, private val alarmScheduler: AlarmScheduler ) : ViewModel() { private val _tabsStates = repository.scaffoldConfig() @@ -100,17 +100,4 @@ class ScheduleListViewModel( fun markAsFavorite(context: Context, talkItem: TalkItemUi) = viewModelScope.launch { alarmScheduler.schedule(context, talkItem) } - - object Factory { - fun create( - repository: AgendaRepository, - alarmScheduler: AlarmScheduler - ) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = ScheduleListViewModel( - repository = repository, - alarmScheduler = alarmScheduler - ) as T - } - } } diff --git a/theme-m3/speakers/speakers-di/.gitignore b/theme-m3/speakers/speakers-di/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/theme-m3/speakers/speakers-di/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/theme-m3/speakers/speakers-di/build.gradle.kts b/theme-m3/speakers/speakers-di/build.gradle.kts new file mode 100644 index 000000000..2b520315c --- /dev/null +++ b/theme-m3/speakers/speakers-di/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + id("conferences4hall.android.library") + id("conferences4hall.quality") +} + +android { + namespace = "org.gdglille.devfest.android.theme.m3.speakers.di" +} + +dependencies { + implementation(projects.themeM3.speakers.speakersFeature) + implementation(projects.shared.coreDi) + + implementation(libs.koin.core) + implementation(libs.koin.android) +} diff --git a/theme-m3/speakers/speakers-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/di/SpeakersModule.kt b/theme-m3/speakers/speakers-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/di/SpeakersModule.kt new file mode 100644 index 000000000..03ffe3392 --- /dev/null +++ b/theme-m3/speakers/speakers-di/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/di/SpeakersModule.kt @@ -0,0 +1,12 @@ +package org.gdglille.devfest.android.theme.m3.speakers.di + +import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakerDetailViewModel +import org.gdglille.devfest.android.theme.m3.speakers.feature.SpeakersListViewModel +import org.gdglille.devfest.repositoriesModule +import org.koin.dsl.module + +val speakersModule = module { + includes(repositoriesModule) + single { SpeakersListViewModel(get()) } + single { parameters -> SpeakerDetailViewModel(parameters.get(), get(), get()) } +} diff --git a/theme-m3/speakers/speakers-feature/build.gradle.kts b/theme-m3/speakers/speakers-feature/build.gradle.kts index 5a99a690e..efc66fbac 100644 --- a/theme-m3/speakers/speakers-feature/build.gradle.kts +++ b/theme-m3/speakers/speakers-feature/build.gradle.kts @@ -14,6 +14,8 @@ dependencies { implementation(projects.themeM3.navigation) implementation(projects.themeM3.style.theme) + implementation(libs.koin.androidx.compose) + implementation(libs.kotlinx.collections) implementation(platform(libs.androidx.compose.bom)) diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt index 6f33afe97..5f59c2ef8 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailOrientableVM.kt @@ -11,26 +11,21 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel -import org.gdglille.devfest.AlarmScheduler import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakerDetailOrientable import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.appbars.TopAppBar -import org.gdglille.devfest.repositories.AgendaRepository +import org.koin.androidx.compose.koinViewModel +import org.koin.core.parameter.parametersOf @OptIn(ExperimentalMaterial3Api::class) @Composable fun SpeakerDetailOrientableVM( speakerId: String, - agendaRepository: AgendaRepository, - alarmScheduler: AlarmScheduler, onTalkClicked: (id: String) -> Unit, onLinkClicked: (url: String) -> Unit, onBackClicked: () -> Unit, modifier: Modifier = Modifier, - viewModel: SpeakerDetailViewModel = viewModel( - factory = SpeakerDetailViewModel.Factory.create(speakerId, agendaRepository, alarmScheduler) - ) + viewModel: SpeakerDetailViewModel = koinViewModel(parameters = { parametersOf(speakerId) }) ) { val context = LocalContext.current val uiState = viewModel.uiState.collectAsState() diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailViewModel.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailViewModel.kt index 845eff816..cb2af70d9 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailViewModel.kt +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakerDetailViewModel.kt @@ -3,12 +3,14 @@ package org.gdglille.devfest.android.theme.m3.speakers.feature import android.annotation.SuppressLint import android.content.Context import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.ktx.crashlytics import com.google.firebase.ktx.Firebase -import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.gdglille.devfest.AlarmScheduler import org.gdglille.devfest.models.ui.SpeakerUi @@ -22,41 +24,24 @@ sealed class SpeakerUiState { } class SpeakerDetailViewModel( - private val speakerId: String, - private val repository: AgendaRepository, + speakerId: String, + repository: AgendaRepository, private val alarmScheduler: AlarmScheduler ) : ViewModel() { - private val _uiState = MutableStateFlow(SpeakerUiState.Loading(SpeakerUi.fake)) - val uiState: StateFlow = _uiState - - init { - viewModelScope.launch { - try { - repository.speaker(speakerId).collect { - _uiState.value = SpeakerUiState.Success(it) - } - } catch (error: Throwable) { - Firebase.crashlytics.recordException(error) - _uiState.value = SpeakerUiState.Failure(error) - } + val uiState: StateFlow = repository.speaker(speakerId) + .map { SpeakerUiState.Success(it) } + .catch { + Firebase.crashlytics.recordException(it) + SpeakerUiState.Failure(it) } - } + .stateIn( + scope = viewModelScope, + initialValue = SpeakerUiState.Loading(SpeakerUi.fake), + started = SharingStarted.WhileSubscribed() + ) @SuppressLint("UnspecifiedImmutableFlag") fun markAsFavorite(context: Context, talkItem: TalkItemUi) = viewModelScope.launch { alarmScheduler.schedule(context, talkItem) } - - object Factory { - fun create( - speakerId: String, - repository: AgendaRepository, - alarmScheduler: AlarmScheduler - ) = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - @Suppress("UNCHECKED_CAST") - return SpeakerDetailViewModel(speakerId, repository, alarmScheduler) as T - } - } - } } diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListCompactVM.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListCompactVM.kt index e6b14298f..863316a93 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListCompactVM.kt +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListCompactVM.kt @@ -10,11 +10,10 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.stringResource -import androidx.lifecycle.viewmodel.compose.viewModel import org.gdglille.devfest.android.theme.m3.speakers.screens.SpeakersListScreen import org.gdglille.devfest.android.theme.m3.style.R import org.gdglille.devfest.android.theme.m3.style.Scaffold -import org.gdglille.devfest.repositories.SpeakerRepository +import org.koin.androidx.compose.koinViewModel private const val ColumnCountLandscape = 4 private const val ColumnCountPortrait = 2 @@ -22,12 +21,9 @@ private const val ColumnCountPortrait = 2 @OptIn(ExperimentalFoundationApi::class) @Composable fun SpeakersListCompactVM( - speakerRepository: SpeakerRepository, onSpeakerClicked: (id: String) -> Unit, modifier: Modifier = Modifier, - viewModel: SpeakersListViewModel = viewModel( - factory = SpeakersListViewModel.Factory.create(speakerRepository) - ) + viewModel: SpeakersListViewModel = koinViewModel() ) { val configuration = LocalConfiguration.current val state = rememberLazyGridState() diff --git a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListViewModel.kt b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListViewModel.kt index 2abae9bd7..98c879406 100644 --- a/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListViewModel.kt +++ b/theme-m3/speakers/speakers-feature/src/main/kotlin/org/gdglille/devfest/android/theme/m3/speakers/feature/SpeakersListViewModel.kt @@ -8,9 +8,14 @@ import com.google.firebase.ktx.Firebase import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.gdglille.devfest.models.ui.SpeakerItemUi +import org.gdglille.devfest.models.ui.SpeakerUi import org.gdglille.devfest.repositories.SpeakerRepository sealed class SpeakersUiState { @@ -19,32 +24,20 @@ sealed class SpeakersUiState { data class Failure(val throwable: Throwable) : SpeakersUiState() } -class SpeakersListViewModel(private val repository: SpeakerRepository) : ViewModel() { - private val _uiState = MutableStateFlow( - SpeakersUiState.Loading( - persistentListOf(SpeakerItemUi.fake.copy(id = "1"), SpeakerItemUi.fake.copy(id = "2")) - ) - ) - val uiState: StateFlow = _uiState - - init { - viewModelScope.launch { - try { - repository.speakers().collect { - _uiState.value = SpeakersUiState.Success(it) - } - } catch (error: Throwable) { - Firebase.crashlytics.recordException(error) - _uiState.value = SpeakersUiState.Failure(error) - } +class SpeakersListViewModel(repository: SpeakerRepository) : ViewModel() { + val uiState: StateFlow = repository.speakers() + .map { SpeakersUiState.Success(it) } + .catch { + Firebase.crashlytics.recordException(it) + SpeakersUiState.Failure(it) } - } - - object Factory { - fun create(repository: SpeakerRepository) = object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = - SpeakersListViewModel(repository = repository) as T - } - } + .stateIn( + scope = viewModelScope, + initialValue = SpeakersUiState.Loading( + persistentListOf( + SpeakerItemUi.fake.copy(id = "1"), SpeakerItemUi.fake.copy(id = "2") + ) + ), + started = SharingStarted.WhileSubscribed() + ) }