diff --git a/composeApp/src/androidMain/kotlin/io/middlepoint/morestuff/shared/MainActivity.kt b/composeApp/src/androidMain/kotlin/io/middlepoint/morestuff/shared/MainActivity.kt index ae95ea706..995a91b5d 100644 --- a/composeApp/src/androidMain/kotlin/io/middlepoint/morestuff/shared/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/io/middlepoint/morestuff/shared/MainActivity.kt @@ -40,10 +40,11 @@ class MainActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, false) enableEdgeToEdge() val rootRouterContext: RouterContext = defaultRouterContext() + val supabase = get() val launchScreen = handleLaunchIntent(intent) FileKit.init(this) - val supabase = get() + supabase.handleDeeplinks(intent) setContent { @@ -101,6 +102,18 @@ class MainActivity : AppCompatActivity() { } } + Intent.ACTION_VIEW -> { + + val uri = intent.data + Logger.d("DeepLink URI: $uri") + + if (uri?.scheme == "app.morestuff" && uri.host == "login-callback") { + Screen.Home + } else { + null + } + } + ACTION_NOTIFICATION_REMINDER -> { intent.getStringExtra(EXTRA_TASK_ID)?.let { Screen.TaskChat(Uuid(it)) diff --git a/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/domain/service/NavigationHelper.kt b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/domain/service/NavigationHelper.kt index fad0bb8c4..5518b0538 100644 --- a/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/domain/service/NavigationHelper.kt +++ b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/domain/service/NavigationHelper.kt @@ -19,6 +19,7 @@ class NavigationHelper: ViewModel(), KoinComponent { val shareable = MutableSharedFlow() val navigation = MutableSharedFlow() + val code = MutableSharedFlow() private val cancelActiveScheduleUseCase: CancelActiveScheduleUseCase by inject() @@ -65,6 +66,21 @@ class NavigationHelper: ViewModel(), KoinComponent { } } + fun navigateToHome(accessToken: String? = null) { + logger.d { "este es el accessToken: $accessToken" } + viewModelScope.launch { + if (accessToken != null) { + logger.d { "Emitting Screen.Home with access token" } + code.emit(accessToken) + navigation.emit(Screen.Home) + } else { + logger.d { "Emitting Screen.Home without token" } + navigation.emit(Screen.Home) + } + } + } + + } diff --git a/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/App.kt b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/App.kt index 646fd4e81..f5f2f9732 100644 --- a/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/App.kt +++ b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/App.kt @@ -13,8 +13,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import co.touchlab.kermit.Logger import com.arkivanov.decompose.router.stack.navigate +import io.github.jan.supabase.SupabaseClient import io.github.xxfast.decompose.router.stack.Router import io.github.xxfast.decompose.router.stack.rememberRouter import io.middlepoint.morestuff.shared.domain.nav.Screen @@ -25,14 +25,25 @@ import io.middlepoint.morestuff.shared.ui.screen.main.MainEvent import io.middlepoint.morestuff.shared.ui.screen.main.MainViewModel import io.middlepoint.morestuff.shared.ui.screen.settings.koinInjectOnRoute import io.middlepoint.morestuff.shared.ui.theme.MoreStuffTheme +import io.middlepoint.morestuff.shared.ui.utils.handleDeeplinkFragment import org.koin.compose.KoinContext +import org.koin.compose.koinInject @Composable -fun App(screen: Screen? = null) { +fun App(screen: Screen? = null, accessToken: String? = null) { KoinContext { val router: Router = rememberRouter { listOf(Screen.Home) } val viewModel = koinInjectOnRoute(MainViewModel::class) val model by viewModel.models.collectAsState() + val supabase: SupabaseClient = koinInject() + + LaunchedEffect(accessToken) { + if (!accessToken.isNullOrBlank()) { + supabase.handleDeeplinkFragment(accessToken) { session -> + viewModel.take(MainEvent.OnBoardingComplete) + } + } + } ProvideAppTheme(model.theme) { MoreStuffTheme { @@ -59,13 +70,23 @@ fun App(screen: Screen? = null) { } } } - LaunchedEffect(screen) { if (screen != null) { - router.navigate { - listOf(Screen.Home, screen) + if (screen == Screen.Home) { + router.navigate { listOf(screen) } + } else { + router.navigate { listOf(Screen.Home, screen) } } } } + + + /* LaunchedEffect(screen) { + if (screen != null) { + router.navigate { + listOf(Screen.Home, screen) + } + } + }*/ } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/utils/handleDeeplinkIos.kt b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/utils/handleDeeplinkIos.kt new file mode 100644 index 000000000..02cda8b84 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/io/middlepoint/morestuff/shared/ui/utils/handleDeeplinkIos.kt @@ -0,0 +1,43 @@ +package io.middlepoint.morestuff.shared.ui.utils + +import io.github.jan.supabase.SupabaseClient +import io.github.jan.supabase.annotations.SupabaseInternal +import io.github.jan.supabase.auth.FlowType +import io.github.jan.supabase.auth.auth +import io.github.jan.supabase.auth.parseFragmentAndImportSession +import io.github.jan.supabase.auth.user.UserSession + +@OptIn(SupabaseInternal::class) +suspend fun SupabaseClient.handleDeeplinkFragment( + fragmentOrUrl: String, + onSessionSuccess: (UserSession) -> Unit = {} +) { + when (auth.config.flowType) { + FlowType.IMPLICIT -> { + auth.parseFragmentAndImportSession(fragmentOrUrl, onSessionSuccess) + + } + + FlowType.PKCE -> { + val code = extractAccessToken(fragmentOrUrl) ?: return + + auth.exchangeCodeForSession(code) + val session = auth.currentSessionOrNull() ?: error("No session available after code exchange") + onSessionSuccess(session) + } + } +} + +private fun extractQueryParam(url: String, param: String): String? { + val queryPart = url.substringAfter('?', missingDelimiterValue = "") + return queryPart + .split('&').firstNotNullOfOrNull { + val parts = it.split('=') + if (parts.size == 2 && parts[0] == param) parts[1] else null + } +} + +private fun extractAccessToken(url: String): String? { + val fragment = url.substringAfter('#', "") + return extractQueryParam(fragment, "access_token") +} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/MainViewController.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt index dc582966d..a09de7816 100644 --- a/composeApp/src/iosMain/kotlin/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -19,51 +19,58 @@ import org.koin.compose.koinInject @OptIn(ExperimentalDecomposeApi::class) fun MainViewController(routerContext: RouterContext) = ComposeUIViewController { - CompositionLocalProvider(LocalRouterContext provides routerContext) { + CompositionLocalProvider(LocalRouterContext provides routerContext) { + val navigationHelper = koinInject() + val initialScreen = remember { mutableStateOf(null) } + val accessToken = remember { mutableStateOf(null) } + LaunchedEffect(Unit) { + navigationHelper.shareable.collect { shareable -> + initialScreen.value = when (shareable) { + is Shareable.Image -> { + Screen.Share(shareable, shareable.uri) + } - val navigationHelper = koinInject() - val initialScreen = remember { mutableStateOf(null) } + is Shareable.Pdf -> { + Screen.Share(shareable, shareable.uri) + } - LaunchedEffect(Unit) { - navigationHelper.shareable.collect { shareable -> - initialScreen.value = when (shareable) { - is Shareable.Image -> { - Screen.Share(shareable, shareable.uri) - } + is Shareable.Text -> { + Screen.Share(shareable, shareable.message) + } - is Shareable.Pdf -> { - Screen.Share(shareable, shareable.uri) - } + else -> { + null + } + } + } + } + LaunchedEffect(Unit) { + navigationHelper.navigation.collect { screen -> + initialScreen.value = screen + } + } - is Shareable.Text -> { - Screen.Share(shareable, shareable.message) - } + LaunchedEffect(Unit) { + navigationHelper.code.collect { code -> + accessToken.value = code + } + } - else -> { - null - } - } - } - } - LaunchedEffect(Unit) { - navigationHelper.navigation.collect { screen -> - initialScreen.value = screen - } - } - PredictiveBackGestureOverlay( - backDispatcher = routerContext.backHandler as BackDispatcher, - backIcon = { progress, _ -> - /*PredictiveBackGestureIcon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - progress = progress, - )*/ - }, - modifier = Modifier.fillMaxSize(), - ){ - App(screen = initialScreen.value) - } + PredictiveBackGestureOverlay( + backDispatcher = routerContext.backHandler as BackDispatcher, + backIcon = { progress, _ -> + /*PredictiveBackGestureIcon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + progress = progress, + )*/ + }, + modifier = Modifier.fillMaxSize(), + ) { + App(screen = initialScreen.value, accessToken = accessToken.value) } + + } } diff --git a/composeApp/src/iosMain/kotlin/io/middlepoint/morestuff/shared/domain/service/Scheduler.ios.kt b/composeApp/src/iosMain/kotlin/io/middlepoint/morestuff/shared/domain/service/Scheduler.ios.kt index 60e3ba1b4..6908e95fd 100644 --- a/composeApp/src/iosMain/kotlin/io/middlepoint/morestuff/shared/domain/service/Scheduler.ios.kt +++ b/composeApp/src/iosMain/kotlin/io/middlepoint/morestuff/shared/domain/service/Scheduler.ios.kt @@ -91,9 +91,7 @@ class SchedulerImpl( }*/ } -/* override fun cancelSchedule(scheduleId: Uuid) { - TODO("Not yet implemented") - }*/ + override fun cancelSchedule(scheduleId: Uuid) { UNUserNotificationCenter.currentNotificationCenter() @@ -146,15 +144,6 @@ class SchedulerImpl( } }*/ - override fun cancelSchedule(scheduleId: Uuid) { - UNUserNotificationCenter.currentNotificationCenter() - .removePendingNotificationRequestsWithIdentifiers( - listOf(getScheduleWorkTag(scheduleId)) - ) - logger.i { "Cancelled notification with scheduleId= $scheduleId" } - - } - override fun cancelPlannedPriorityUpdate() { UNUserNotificationCenter.currentNotificationCenter() .removePendingNotificationRequestsWithIdentifiers( diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index 6d93e8e81..4703c25bd 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -28,6 +28,7 @@ CFBundleURLSchemes morestuff + app.morestuff com.googleusercontent.apps.895634462091-qr6urlar6kjek2q0k3s7m1ctse48vgpf diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index 6093af0cc..c28a9b5b1 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -20,8 +20,18 @@ struct SwiftUIApp: App { .ignoresSafeArea(edges: .all) .ignoresSafeArea(.keyboard) .onOpenURL { url in + print("url type: \(url)") - handleFileURL(url, navigationHelper: navigationHelper) + if url.absoluteString.contains("app.morestuff://login-callback") && url.absoluteString.contains("access_token=") { + if let fragment = url.fragment { + print("Fragment: \(fragment)") + delegate.navigateHome(accessToken: fragment) + } else { + print("No fragment found in URL") + } + } else { + handleFileURL(url, navigationHelper: navigationHelper) + } } } @@ -36,6 +46,7 @@ struct SwiftUIApp: App { } } + private func handleFileURL(_ url: URL, navigationHelper: NavigationHelper) { let filePath: String? @@ -146,6 +157,13 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele self.navigationHelper.cancelTaskSchedule(taskId: taskId) } } + + func navigateHome(accessToken: String? = nil) { + DispatchQueue.main.async { + print("token de acceso: \(accessToken)") + self.navigationHelper.navigateToHome(accessToken: accessToken) + } + }