diff --git a/androidApp/src/main/java/fr/paug/androidmakers/AndroidMakersApplication.kt b/androidApp/src/main/java/fr/paug/androidmakers/AndroidMakersApplication.kt index 2d58aae3..6bd2a842 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/AndroidMakersApplication.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/AndroidMakersApplication.kt @@ -22,8 +22,6 @@ import io.openfeedback.android.OpenFeedback class AndroidMakersApplication : Application() { - lateinit var urlOpener: UrlOpener - lateinit var openFeedback: OpenFeedback override fun onCreate() { @@ -34,8 +32,6 @@ class AndroidMakersApplication : Application() { listOf(viewModelModule) ) - urlOpener = UrlOpener(this) - openFeedback = OpenFeedback( context = this, openFeedbackProjectId = "am2023", diff --git a/androidApp/src/main/java/fr/paug/androidmakers/di/ViewModelModule.kt b/androidApp/src/main/java/fr/paug/androidmakers/di/ViewModelModule.kt index e7f0fb7c..493f8321 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/di/ViewModelModule.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/di/ViewModelModule.kt @@ -14,10 +14,10 @@ import org.koin.dsl.module val viewModelModule = module { viewModel { MainActivityViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { PartnersViewModel(get()) } - viewModel { AgendaPagerViewModel(get(), get()) } + viewModel { AgendaPagerViewModel(get(), get(), get()) } viewModel { AgendaLayoutViewModel(get()) } viewModel { SpeakerViewModel(get()) } viewModel { SpeakerDetailsViewModel(get(), get()) } - viewModel { SessionDetailViewModel(get(), get(), get(), get(), get())} + viewModel { SessionDetailViewModel(get(), get(), get(), get(), get(), get())} viewModel { VenueViewModel(get(), get()) } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivity.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivity.kt index b26320f3..e4aad42e 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivity.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivity.kt @@ -143,8 +143,7 @@ class MainActivity : AppCompatActivity() { val result = auth.signInWithCredential(firebaseCredential) // Sign in success, update UI with the signed-in user's information lifecycleScope.launch { - viewModel._user.value = result.user?.toUser() - viewModel.syncBookmarksUseCase(auth.currentUser!!.uid) + viewModel.setUser(result.user?.toUser()) } } } @@ -153,7 +152,9 @@ class MainActivity : AppCompatActivity() { } } catch (e: ApiException) { e.printStackTrace() - viewModel._user.value = null + lifecycleScope.launch { + viewModel.setUser(null) + } } } } @@ -178,7 +179,7 @@ class MainActivity : AppCompatActivity() { Firebase.auth.signOut() googleSignInClient.signOut() googleSignInClient.revokeAccess() - viewModel._user.emit(null) + viewModel.setUser(null) } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivityViewModel.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivityViewModel.kt index 763c1408..9d94d557 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivityViewModel.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/MainActivityViewModel.kt @@ -1,7 +1,6 @@ package fr.paug.androidmakers.ui import androidx.lifecycle.ViewModel -import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewModelScope import fr.androidmakers.domain.interactor.OpenCocUseCase import fr.androidmakers.domain.interactor.OpenFaqUseCase @@ -25,7 +24,7 @@ class MainActivityViewModel( val openCocUseCase: OpenCocUseCase ): ViewModel() { - val _user = MutableStateFlow(null) + private val _user = MutableStateFlow(null) val user: Flow = _user init { @@ -40,4 +39,11 @@ class MainActivityViewModel( } } } + + suspend fun setUser(user: User?) { + _user.emit(user) + user?.let { + syncBookmarksUseCase(it.id) + } + } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/AVALayout.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/AVALayout.kt index 29ee2b4c..33c1139d 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/AVALayout.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/AVALayout.kt @@ -51,6 +51,7 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import at.asitplus.KmmResult +import fr.androidmakers.domain.interactor.GetPartnersUseCase import fr.androidmakers.domain.model.PartnerGroup import fr.androidmakers.domain.model.SpeakerId import fr.androidmakers.domain.model.User @@ -286,10 +287,10 @@ private fun AVANavHost( } class PartnersViewModel( - private val partnersRepository: PartnersRepository + private val getPartnersUseCase: GetPartnersUseCase ) : LceViewModel>() { override fun produce(): Flow>> { - return partnersRepository.getPartners() + return getPartnersUseCase() } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaColumn.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaColumn.kt index f612e463..afcf801d 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaColumn.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaColumn.kt @@ -33,6 +33,7 @@ import fr.paug.androidmakers.ui.util.stringResource fun AgendaColumn( sessionsPerStartTime: Map>, onSessionClicked: (UISession) -> Unit, + onSessionBookmarked: (UISession, Boolean) -> Unit, ) { val listState = rememberLazyListState() @@ -64,9 +65,7 @@ fun AgendaColumn( modifier = Modifier.animateItemPlacement(), uiSession = uiSession, onSessionClicked = onSessionClicked, - onSessionBookmarked = { session, bookmarked -> - - } + onSessionBookmarked = onSessionBookmarked ) } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPager.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPager.kt index 72332069..191feacf 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPager.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPager.kt @@ -93,7 +93,10 @@ fun AgendaPager( } AgendaColumn( sessionsPerStartTime = addSeparators(LocalContext.current, items), - onSessionClicked = onSessionClicked + onSessionClicked = onSessionClicked, + onSessionBookmarked = { uiSession, bookmarked -> + viewModel.setSessionBookmark(uiSession, bookmarked) + } ) } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPagerViewModel.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPagerViewModel.kt index eac33ed3..55dcaa78 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPagerViewModel.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/agenda/AgendaPagerViewModel.kt @@ -1,24 +1,36 @@ package fr.paug.androidmakers.ui.components.agenda +import androidx.lifecycle.viewModelScope import at.asitplus.KmmResult import fr.androidmakers.domain.interactor.GetAgendaUseCase -import fr.androidmakers.domain.interactor.SyncBookmarksUseCase +import fr.androidmakers.domain.interactor.GetFavoriteSessionsUseCase +import fr.androidmakers.domain.interactor.SetSessionBookmarkUseCase import fr.androidmakers.domain.model.Agenda +import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.repo.BookmarksRepository +import fr.paug.androidmakers.ui.model.UISession import fr.paug.androidmakers.ui.viewmodel.LceViewModel import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime class AgendaPagerViewModel( private val getAgendaUseCase: GetAgendaUseCase, - private val bookmarksRepository: BookmarksRepository + private val setSessionBookmarkUseCase: SetSessionBookmarkUseCase, + private val getFavoriteSessionsUseCase: GetFavoriteSessionsUseCase ) : LceViewModel() { override fun produce(): Flow> { return getAgendaUseCase() } - fun getFavoriteSessions() = bookmarksRepository.getFavoriteSessions() + fun getFavoriteSessions() = getFavoriteSessionsUseCase() init { launch(false) } + + fun setSessionBookmark(uiSession: UISession, bookmark: Boolean) = viewModelScope.launch { + setSessionBookmarkUseCase(uiSession.id, bookmark) + } } diff --git a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/session/SessionDetailViewModel.kt b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/session/SessionDetailViewModel.kt index c680d1ad..5764b2a8 100644 --- a/androidApp/src/main/java/fr/paug/androidmakers/ui/components/session/SessionDetailViewModel.kt +++ b/androidApp/src/main/java/fr/paug/androidmakers/ui/components/session/SessionDetailViewModel.kt @@ -3,7 +3,7 @@ package fr.paug.androidmakers.ui.components.session import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.google.firebase.auth.FirebaseAuth +import fr.androidmakers.domain.interactor.SetSessionBookmarkUseCase import fr.androidmakers.domain.model.Session import fr.androidmakers.domain.model.Speaker import fr.androidmakers.domain.repo.BookmarksRepository @@ -12,8 +12,8 @@ import fr.androidmakers.domain.repo.SessionsRepository import fr.androidmakers.domain.repo.SpeakersRepository import fr.paug.androidmakers.ui.viewmodel.Lce import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.mapNotNull @@ -24,10 +24,12 @@ import kotlinx.datetime.toInstant @OptIn(ExperimentalCoroutinesApi::class) class SessionDetailViewModel( savedStateHandle: SavedStateHandle, - private val sessionsRepository: SessionsRepository, + sessionsRepository: SessionsRepository, private val roomsRepository: RoomsRepository, - private val bookmarksRepository: BookmarksRepository, - private val speakersRepository: SpeakersRepository + // TODO remove the reference to repositories here + bookmarksRepository: BookmarksRepository, + private val speakersRepository: SpeakersRepository, + private val setSessionBookmarkUseCase: SetSessionBookmarkUseCase, ) : ViewModel() { private val sessionId: String = savedStateHandle["sessionId"]!! @@ -74,13 +76,6 @@ class SessionDetailViewModel( } fun bookmark(bookmarked: Boolean) = viewModelScope.launch { - bookmarksRepository.setBookmarked(sessionId, bookmarked) - - val userId = FirebaseAuth.getInstance().currentUser?.uid - if (userId != null) { - GlobalScope.launch { - sessionsRepository.setBookmark(userId, sessionId, bookmarked) - } - } + setSessionBookmarkUseCase(session.first().getOrThrow().id, bookmarked) } } diff --git a/iosApp/RobotConf.xcodeproj/project.pbxproj b/iosApp/RobotConf.xcodeproj/project.pbxproj index 655831c5..13f2ec41 100644 --- a/iosApp/RobotConf.xcodeproj/project.pbxproj +++ b/iosApp/RobotConf.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 45EC5A9E29C254A500ABBB7F /* GraphQLTalksProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EC5A9D29C254A500ABBB7F /* GraphQLTalksProvider.swift */; }; 45EC5AC229C26DCE00ABBB7F /* GraphQLDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EC5AC129C26DCE00ABBB7F /* GraphQLDataProvider.swift */; }; 45EC5AC429C2701400ABBB7F /* SessionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45EC5AC329C2701400ABBB7F /* SessionData.swift */; }; + 6418BE5D2BA1EC8900F3EA79 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6418BE5C2BA1EC8900F3EA79 /* GoogleService-Info.plist */; }; 6440520B2B892F0200F87201 /* ResourcesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6440520A2B892F0200F87201 /* ResourcesUtils.swift */; }; 647899322B90937800887B74 /* Speaker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647899312B90937800887B74 /* Speaker.swift */; }; 647899342B9093A000887B74 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647899332B9093A000887B74 /* Session.swift */; }; @@ -112,6 +113,7 @@ 45EC5A9D29C254A500ABBB7F /* GraphQLTalksProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLTalksProvider.swift; sourceTree = ""; }; 45EC5AC129C26DCE00ABBB7F /* GraphQLDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphQLDataProvider.swift; sourceTree = ""; }; 45EC5AC329C2701400ABBB7F /* SessionData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionData.swift; sourceTree = ""; }; + 6418BE5C2BA1EC8900F3EA79 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../Downloads/GoogleService-Info.plist"; sourceTree = ""; }; 6440520A2B892F0200F87201 /* ResourcesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourcesUtils.swift; sourceTree = ""; }; 647899312B90937800887B74 /* Speaker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Speaker.swift; sourceTree = ""; }; 647899332B9093A000887B74 /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; @@ -155,7 +157,6 @@ B2CD2767234D12500016AA02 /* SessionRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionRepository.swift; sourceTree = ""; }; B2CD276B234D13380016AA02 /* Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; B2CD276D234D18450016AA02 /* AgendaDayListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDayListViewModel.swift; sourceTree = ""; }; - B2CD42612357876C009FE6B0 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; B2CD4263235789B5009FE6B0 /* DataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProvider.swift; sourceTree = ""; }; B2CD426823578FBD009FE6B0 /* DocumentSnapshot+Decodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DocumentSnapshot+Decodable.swift"; sourceTree = ""; }; B2ECA77324151B6E00489874 /* OpenFeedback-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "OpenFeedback-Info.plist"; sourceTree = ""; }; @@ -375,7 +376,7 @@ B2F270DA23DC869300FD6823 /* .swiftlint.yml */, B2CD273C234D06560016AA02 /* Info.plist */, B2ECA77324151B6E00489874 /* OpenFeedback-Info.plist */, - B2CD42612357876C009FE6B0 /* GoogleService-Info.plist */, + 6418BE5C2BA1EC8900F3EA79 /* GoogleService-Info.plist */, B2CD2736234D06560016AA02 /* Preview Content */, ); path = RobotConf; @@ -597,6 +598,7 @@ B2F270DB23DC869300FD6823 /* .swiftlint.yml in Resources */, B2CD2738234D06560016AA02 /* Preview Assets.xcassets in Resources */, B2CD2735234D06560016AA02 /* Assets.xcassets in Resources */, + 6418BE5D2BA1EC8900F3EA79 /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/iosApp/RobotConf/AppDelegate.swift b/iosApp/RobotConf/AppDelegate.swift index 1f57085d..85315e18 100644 --- a/iosApp/RobotConf/AppDelegate.swift +++ b/iosApp/RobotConf/AppDelegate.swift @@ -6,6 +6,7 @@ import UIKit import FirebaseAnalytics import FirebaseCrashlytics import Firebase +import shared @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -27,6 +28,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("⚠️ Firebase descriptor for the main purpose is not embedded, crashlytics disabled.") } + DependenciesBuilder().inject(platformModules: []) + return true } diff --git a/iosApp/RobotConf/Model/Model.swift b/iosApp/RobotConf/Model/Model.swift index d9f229a0..295dafee 100644 --- a/iosApp/RobotConf/Model/Model.swift +++ b/iosApp/RobotConf/Model/Model.swift @@ -7,19 +7,6 @@ import shared /// Singleton model private(set) var model = Model() -private(set) var apolloClient = ApolloClientBuilder( - url: "https://androidmakers-2023.ew.r.appspot.com/graphql", - conference: "androidmakers2023", - token: "").build() - -private(set) var datastore = createDataStore(migrations: []) { - do { - let documentDir = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) - return documentDir.path.appending("/bookmarks.preferences_pb") - } catch { - return "" - } -} #if DEBUG private var mockModel = Model(dataProvider: DataProvider(desiredProviderType: .json)) @@ -32,13 +19,6 @@ func injectMockModel() { protocol RepositoryProvider { var sessionRepository: SessionRepository { get } var feedbackRepository: FeedbackRepository { get } - var partnersRepository: PartnersRepository { get } - var getConferenceVenueUC: GetConferenceVenueUseCase { get } - var getAfterpartyVenueUC: GetAfterpartyVenueUseCase { get } - var bookmarksRepository: BookmarksRepository { get } - var openXAccountUC: OpenXAccountUseCase { get } - var openXHashtagUC: OpenXHashtagUseCase { get } - var openYoutubeUC: OpenYoutubeUseCase { get } } /// The model API object @@ -47,30 +27,10 @@ class Model: RepositoryProvider { let sessionRepository: SessionRepository let feedbackRepository: FeedbackRepository - let getConferenceVenueUC: GetConferenceVenueUseCase - let getAfterpartyVenueUC: GetAfterpartyVenueUseCase - let openXAccountUC: OpenXAccountUseCase - let openXHashtagUC: OpenXHashtagUseCase - let openYoutubeUC: OpenYoutubeUseCase - - let partnersRepository: PartnersRepository - let bookmarksRepository: BookmarksRepository - let urlOpener: UrlOpener fileprivate init(dataProvider: DataProvider = DataProvider()) { self.dataProvider = dataProvider sessionRepository = SessionRepository(dataProvider: dataProvider) - let venueRepository = VenueGraphQLRepository(apolloClient: apolloClient) - getConferenceVenueUC = GetConferenceVenueUseCase(venueRepository: venueRepository) - getAfterpartyVenueUC = GetAfterpartyVenueUseCase(venueRepository: venueRepository) - feedbackRepository = FeedbackRepository(dataProvider: dataProvider) - partnersRepository = PartnersGraphQLRepository(apolloClient: apolloClient) - - bookmarksRepository = BookmarksDataStoreRepository(dataStore: datastore) - urlOpener = UrlOpener() - openXAccountUC = OpenXAccountUseCase(urlOpener: urlOpener) - openXHashtagUC = OpenXHashtagUseCase(urlOpener: urlOpener) - openYoutubeUC = OpenYoutubeUseCase(urlOpener: urlOpener) } } diff --git a/iosApp/RobotConf/UI/About/AboutViewModel.swift b/iosApp/RobotConf/UI/About/AboutViewModel.swift index f16d7d86..91a88f5e 100644 --- a/iosApp/RobotConf/UI/About/AboutViewModel.swift +++ b/iosApp/RobotConf/UI/About/AboutViewModel.swift @@ -11,11 +11,11 @@ class AboutViewModel: ObservableObject, Identifiable { @Published var partnerGroups = [PartnerGroup]() - private var disposables = Set() + private let deps = DepContainer() @MainActor func activate() async { - let partnersFlow = model.partnersRepository.getPartners() + let partnersFlow = deps.getPartnersUseCase.invoke() for await partnerResult in partnersFlow { partnerResult.fold( onSuccess: { [weak self] partnerGroups in @@ -29,15 +29,15 @@ class AboutViewModel: ObservableObject, Identifiable { } func openTwitterPage() { - model.openXAccountUC.invoke() + deps.openXAccountUseCase.invoke() } func openHashtagPage() { - model.openXHashtagUC.invoke() + deps.openXHashtagUseCase.invoke() } func openYoutubePage() { - model.openYoutubeUC.invoke() + deps.openYoutubeUseCase.invoke() } func openPartnerPage(_ partner: Partner) { diff --git a/iosApp/RobotConf/UI/Agenda/AgendaDetailViewModel.swift b/iosApp/RobotConf/UI/Agenda/AgendaDetailViewModel.swift index 9f8b50f1..a85583b2 100644 --- a/iosApp/RobotConf/UI/Agenda/AgendaDetailViewModel.swift +++ b/iosApp/RobotConf/UI/Agenda/AgendaDetailViewModel.swift @@ -29,16 +29,14 @@ class AgendaDetailViewModel: ObservableObject, Identifiable { @Published var content: Content? private var sessionRepo: SessionRepository - private var bookmarksRepo: BookmarksRepository private var disposables = Set() + private let deps = DepContainer() init( sessionId: String, - sessionRepo: SessionRepository = model.sessionRepository, - bookmarksRepo: BookmarksRepository = model.bookmarksRepository + sessionRepo: SessionRepository = model.sessionRepository ) { - self.bookmarksRepo = bookmarksRepo self.sessionRepo = sessionRepo sessionRepo.getSessions() @@ -51,7 +49,7 @@ class AgendaDetailViewModel: ObservableObject, Identifiable { self?.content = Content( from: session, - isFavorite: false + isFavorite: false // Will be fixed when the getAgendaUsecase will be ready ) }.store(in: &disposables) } @@ -59,7 +57,7 @@ class AgendaDetailViewModel: ObservableObject, Identifiable { func toggleFavorite(ofSession session: Content) { Task { do { - try await bookmarksRepo.setBookmarked(sessionId: session.sessionId, bookmarked: session.isFavorite) + try await deps.setSessionBookmarkedUseCase.invoke(sessionId: session.sessionId, isBookmark: !session.isFavorite) } catch { print("error") } diff --git a/iosApp/RobotConf/UI/Agenda/ByDay/AgendaDayListViewModel.swift b/iosApp/RobotConf/UI/Agenda/ByDay/AgendaDayListViewModel.swift index 0190307f..dde21cbe 100644 --- a/iosApp/RobotConf/UI/Agenda/ByDay/AgendaDayListViewModel.swift +++ b/iosApp/RobotConf/UI/Agenda/ByDay/AgendaDayListViewModel.swift @@ -38,17 +38,16 @@ class AgendaDayListViewModel: ObservableObject, Identifiable { @Published var favoriteSessions: Set = [] private var sessionRepo: SessionRepository - private var bookmarksRepo: BookmarksRepository private var disposables = Set() private var timer: Timer? private var isDisplayed = false + private let deps = DepContainer() + init( - sessionRepository: SessionRepository = model.sessionRepository, - bookmarksRepository: BookmarksRepository = model.bookmarksRepository + sessionRepository: SessionRepository = model.sessionRepository ) { self.sessionRepo = sessionRepository - self.bookmarksRepo = bookmarksRepository sessionRepo.getSessions().sink { [weak self] in self?.sessionsChanged(sessions: $0) }.store(in: &disposables) @@ -66,7 +65,8 @@ class AgendaDayListViewModel: ObservableObject, Identifiable { @MainActor func activate() async { - for await favoriteSessions in bookmarksRepo.getFavoriteSessions() { + let getFavoriteSessionsUseCase = deps.getFavoritesSessionsUseCase + for await favoriteSessions in getFavoriteSessionsUseCase.invoke() { self.favoriteSessions = favoriteSessions } } @@ -80,10 +80,11 @@ class AgendaDayListViewModel: ObservableObject, Identifiable { func toggleFavorite(ofSession session: Content.Session) { Task { let isBookmarked = !favoriteSessions.contains(session.uid) + let setBookmarkUseCase = deps.setSessionBookmarkedUseCase - try await bookmarksRepo.setBookmarked( + try await setBookmarkUseCase.invoke( sessionId:session.uid, - bookmarked:isBookmarked) + isBookmark:isBookmarked) } } diff --git a/iosApp/RobotConf/UI/Agenda/ByRoom/AgendaRoomListViewModel.swift b/iosApp/RobotConf/UI/Agenda/ByRoom/AgendaRoomListViewModel.swift index eda1a8fd..63c224d0 100644 --- a/iosApp/RobotConf/UI/Agenda/ByRoom/AgendaRoomListViewModel.swift +++ b/iosApp/RobotConf/UI/Agenda/ByRoom/AgendaRoomListViewModel.swift @@ -30,16 +30,15 @@ class AgendaRoomListViewModel: ObservableObject { @Published var selectedDay = Date.distantFuture private var sessionRepo: SessionRepository - private var bookmarksRepo: BookmarksRepository private var isDisplayed = false private var disposables = Set() + private let deps = DepContainer() + init( - sessionRepo: SessionRepository = model.sessionRepository, - bookmarksRepo: BookmarksRepository = model.bookmarksRepository + sessionRepo: SessionRepository = model.sessionRepository ) { self.sessionRepo = sessionRepo - self.bookmarksRepo = bookmarksRepo sessionRepo.getSessions().sink { [unowned self] sessions in var allDates = Set() @@ -71,7 +70,8 @@ class AgendaRoomListViewModel: ObservableObject { @MainActor func activate() async { - for await favSessions in bookmarksRepo.getFavoriteSessions() { + let getFavoriteSessionsUseCase = deps.getFavoritesSessionsUseCase + for await favSessions in getFavoriteSessionsUseCase.invoke() { self.favoriteSessions = favSessions } } diff --git a/iosApp/RobotConf/UI/Location/AfterPartyVenueViewModel.swift b/iosApp/RobotConf/UI/Location/AfterPartyVenueViewModel.swift index 6f5d4021..c1cc5408 100644 --- a/iosApp/RobotConf/UI/Location/AfterPartyVenueViewModel.swift +++ b/iosApp/RobotConf/UI/Location/AfterPartyVenueViewModel.swift @@ -10,15 +10,11 @@ import Foundation import shared class AfterPartyVenueViewModel: LocationVenueViewModel { - - let getAfterpartyUc: GetAfterpartyVenueUseCase - init(getAfterpartyUc: GetAfterpartyVenueUseCase) { - self.getAfterpartyUc = getAfterpartyUc - } override func activate() async { - for await afterPartyVenueResult in self.getAfterpartyUc.invoke() { + let getAfterpartyUc = DepContainer().getAfterpartyVenueUseCase + for await afterPartyVenueResult in getAfterpartyUc.invoke() { afterPartyVenueResult.fold( onSuccess: { [weak self] venue in self?.content = venue != nil ? Content(from: venue!) : nil diff --git a/iosApp/RobotConf/UI/Location/ConferenceVenueViewModel.swift b/iosApp/RobotConf/UI/Location/ConferenceVenueViewModel.swift index 78b5c1f9..2e0843a0 100644 --- a/iosApp/RobotConf/UI/Location/ConferenceVenueViewModel.swift +++ b/iosApp/RobotConf/UI/Location/ConferenceVenueViewModel.swift @@ -10,14 +10,10 @@ import Foundation import shared class ConferenceVenueViewModel: LocationVenueViewModel { - let getConferenceVenueUc: GetConferenceVenueUseCase - - init(getConferenceVenueUc: GetConferenceVenueUseCase) { - self.getConferenceVenueUc = getConferenceVenueUc - } override func activate() async { - for await confVenueResult in self.getConferenceVenueUc.invoke() { + let getConferenceVenueUc = DepContainer().getConferenceVenueUseCase + for await confVenueResult in getConferenceVenueUc.invoke() { confVenueResult.fold( onSuccess: { [weak self] venue in self?.content = venue != nil ? Content(from: venue!) : nil diff --git a/iosApp/RobotConf/UI/Location/LocationVenueView.swift b/iosApp/RobotConf/UI/Location/LocationVenueView.swift index 5e82ac15..994d5e7c 100644 --- a/iosApp/RobotConf/UI/Location/LocationVenueView.swift +++ b/iosApp/RobotConf/UI/Location/LocationVenueView.swift @@ -14,13 +14,9 @@ struct LocationVenueView: View { init(kind: LocationVenueViewModel.VenueKind) { switch kind { case .conference: - self._viewModel = StateObject(wrappedValue: ConferenceVenueViewModel( - getConferenceVenueUc: model.getConferenceVenueUC - )) + self._viewModel = StateObject(wrappedValue: ConferenceVenueViewModel()) case .party: - self._viewModel = StateObject(wrappedValue: AfterPartyVenueViewModel( - getAfterpartyUc: model.getAfterpartyVenueUC - )) + self._viewModel = StateObject(wrappedValue: AfterPartyVenueViewModel()) } } diff --git a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt index 3b21cd83..46955750 100644 --- a/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt +++ b/shared/data/src/commonMain/kotlin/fr/androidmakers/store/firebase/FirebaseUserRepository.kt @@ -7,6 +7,10 @@ import fr.androidmakers.domain.repo.UserRepository class FirebaseUserRepository : UserRepository { override suspend fun getUser(): User? { - return Firebase.auth.currentUser?.toUser() + return try { + Firebase.auth.currentUser?.toUser() + } catch (e: Exception) { + null + } } } diff --git a/shared/di/src/commonMain/kotlin/fr/androidmakers/di/DomainModule.kt b/shared/di/src/commonMain/kotlin/fr/androidmakers/di/DomainModule.kt index c79f5043..ee11d8d7 100644 --- a/shared/di/src/commonMain/kotlin/fr/androidmakers/di/DomainModule.kt +++ b/shared/di/src/commonMain/kotlin/fr/androidmakers/di/DomainModule.kt @@ -3,11 +3,14 @@ package fr.androidmakers.di import fr.androidmakers.domain.interactor.GetAfterpartyVenueUseCase import fr.androidmakers.domain.interactor.GetAgendaUseCase import fr.androidmakers.domain.interactor.GetConferenceVenueUseCase +import fr.androidmakers.domain.interactor.GetFavoriteSessionsUseCase +import fr.androidmakers.domain.interactor.GetPartnersUseCase import fr.androidmakers.domain.interactor.OpenCocUseCase import fr.androidmakers.domain.interactor.OpenFaqUseCase import fr.androidmakers.domain.interactor.OpenXAccountUseCase import fr.androidmakers.domain.interactor.OpenXHashtagUseCase import fr.androidmakers.domain.interactor.OpenYoutubeUseCase +import fr.androidmakers.domain.interactor.SetSessionBookmarkUseCase import fr.androidmakers.domain.interactor.SyncBookmarksUseCase import org.koin.core.module.Module import org.koin.dsl.module @@ -24,4 +27,7 @@ val domainModule = module { factory { OpenYoutubeUseCase(get()) } factory { OpenXHashtagUseCase(get()) } factory { OpenXAccountUseCase(get()) } + factory { SetSessionBookmarkUseCase(get(), get(), get()) } + factory { GetPartnersUseCase(get()) } + factory { GetFavoriteSessionsUseCase(get()) } } diff --git a/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DataModule.ios.kt b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DataModule.ios.kt index be0f95ac..e407396a 100644 --- a/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DataModule.ios.kt +++ b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DataModule.ios.kt @@ -1,8 +1,30 @@ package fr.androidmakers.di +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences import fr.androidmakers.store.graphql.ApolloClientBuilder +import fr.androidmakers.store.local.createDataStore +import kotlinx.cinterop.ExperimentalForeignApi import org.koin.dsl.module +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSURL +import platform.Foundation.NSUserDomainMask +@OptIn(ExperimentalForeignApi::class) actual val dataPlatformModule = module { single { ApolloClientBuilder("https://androidmakers-2023.ew.r.appspot.com/graphql", "androidmakers2023", "") } + + single> { + createDataStore { + val documentDirectory: NSURL? = NSFileManager.defaultManager.URLForDirectory( + directory = NSDocumentDirectory, + inDomain = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null, + ) + requireNotNull(documentDirectory).path + "/bookmarks.preferences_pb" + } + } } diff --git a/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DepContainer.kt b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DepContainer.kt new file mode 100644 index 00000000..4521e58a --- /dev/null +++ b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DepContainer.kt @@ -0,0 +1,27 @@ +package fr.androidmakers.di + +import fr.androidmakers.domain.interactor.GetAfterpartyVenueUseCase +import fr.androidmakers.domain.interactor.GetConferenceVenueUseCase +import fr.androidmakers.domain.interactor.GetFavoriteSessionsUseCase +import fr.androidmakers.domain.interactor.GetPartnersUseCase +import fr.androidmakers.domain.interactor.OpenFaqUseCase +import fr.androidmakers.domain.interactor.OpenXAccountUseCase +import fr.androidmakers.domain.interactor.OpenXHashtagUseCase +import fr.androidmakers.domain.interactor.OpenYoutubeUseCase +import fr.androidmakers.domain.interactor.SetSessionBookmarkUseCase +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class DepContainer: KoinComponent { + val getConferenceVenueUseCase: GetConferenceVenueUseCase by inject() + val getAfterpartyVenueUseCase: GetAfterpartyVenueUseCase by inject() + + val openXAccountUseCase: OpenXAccountUseCase by inject() + val openXHashtagUseCase: OpenXHashtagUseCase by inject() + val openYoutubeUseCase: OpenYoutubeUseCase by inject() + val openFaqUseCase: OpenFaqUseCase by inject() + + val getPartnersUseCase: GetPartnersUseCase by inject() + val setSessionBookmarkedUseCase: SetSessionBookmarkUseCase by inject() + val getFavoritesSessionsUseCase: GetFavoriteSessionsUseCase by inject() +} diff --git a/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DomainModule.ios.kt b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DomainModule.ios.kt index 7d229cab..512ed212 100644 --- a/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DomainModule.ios.kt +++ b/shared/di/src/iosMain/kotlin/fr/androidmakers/di/DomainModule.ios.kt @@ -1,6 +1,10 @@ package fr.androidmakers.di -import org.koin.core.module.Module +import fr.androidmakers.domain.utils.UrlOpener +import org.koin.dsl.module -actual val domainPlatformModule: Module - get() = TODO("Not yet implemented") +actual val domainPlatformModule = module { + single { + UrlOpener() + } +} diff --git a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetFavoriteSessionsUseCase.kt b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetFavoriteSessionsUseCase.kt new file mode 100644 index 00000000..fe299194 --- /dev/null +++ b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetFavoriteSessionsUseCase.kt @@ -0,0 +1,10 @@ +package fr.androidmakers.domain.interactor + +import fr.androidmakers.domain.repo.BookmarksRepository + +// TODO temporary, should be merge with the getAgenda and/or getsessions +class GetFavoriteSessionsUseCase( + private val bookmarksRepository: BookmarksRepository +) { + operator fun invoke() = bookmarksRepository.getFavoriteSessions() +} diff --git a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetPartnersUseCase.kt b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetPartnersUseCase.kt new file mode 100644 index 00000000..371d92db --- /dev/null +++ b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/GetPartnersUseCase.kt @@ -0,0 +1,9 @@ +package fr.androidmakers.domain.interactor + +import fr.androidmakers.domain.repo.PartnersRepository + +class GetPartnersUseCase( + private val partnersRepository: PartnersRepository +) { + operator fun invoke() = partnersRepository.getPartners() +} diff --git a/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/SetSessionBookmarkUseCase.kt b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/SetSessionBookmarkUseCase.kt new file mode 100644 index 00000000..62c952be --- /dev/null +++ b/shared/domain/src/commonMain/kotlin/fr/androidmakers/domain/interactor/SetSessionBookmarkUseCase.kt @@ -0,0 +1,19 @@ +package fr.androidmakers.domain.interactor + +import fr.androidmakers.domain.repo.BookmarksRepository +import fr.androidmakers.domain.repo.SessionsRepository +import fr.androidmakers.domain.repo.UserRepository + +class SetSessionBookmarkUseCase( + private val userRepository: UserRepository, + private val sessionsRepository: SessionsRepository, + private val bookmarksRepository: BookmarksRepository +) { + suspend operator fun invoke(sessionId: String, isBookmark: Boolean) { + userRepository.getUser()?.id?.let { token -> + sessionsRepository.setBookmark(token, sessionId, isBookmark) + } + + bookmarksRepository.setBookmarked(sessionId, isBookmark) + } +}