diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/Constants.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/Constants.kt index 370f748..002fd71 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/Constants.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/Constants.kt @@ -2,6 +2,9 @@ package dev.datlag.aniflow.other data object Constants { + const val GITHUB_REPO = "https://github.com/DatL4g/AniFlow" + const val GITHUB_OWNER = "https://github.com/DatL4g" + data object AniList { const val SERVER_URL = "https://graphql.anilist.co/" const val APOLLO_CLIENT = "AniListApolloClient" diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt index 0033eb2..87561b0 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/other/UserHelper.kt @@ -37,7 +37,7 @@ class UserHelper( private val clientId: String ) { - val isLoggedIn: Flow = userSettings.isAniListLoggedIn.distinctUntilChanged() + val isLoggedIn: Flow = userSettings.isAniListLoggedIn.flowOn(ioDispatcher()).distinctUntilChanged() val loginUrl: String = "https://anilist.co/api/v2/oauth/authorize?client_id=$clientId&response_type=token" @OptIn(DelicateCoroutinesApi::class) @@ -133,4 +133,8 @@ class UserHelper( } ) } + + suspend fun logout() { + userSettings.removeAniListToken() + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt index 0049e5a..3b20a4a 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsComponent.kt @@ -9,6 +9,9 @@ import dev.datlag.aniflow.settings.model.CharLanguage as SettingsChar interface SettingsComponent : Component { val user: Flow + val isLoggedIn: Flow + val loginUri: String + val adultContent: Flow val selectedColor: Flow val selectedTitleLanguage: Flow @@ -18,4 +21,5 @@ interface SettingsComponent : Component { fun changeProfileColor(value: SettingsColor?) fun changeTitleLanguage(value: SettingsTitle?) fun changeCharLanguage(value: SettingsChar?) + fun logout() } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt index 91a57f5..44d36ad 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreen.kt @@ -19,8 +19,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.painter.ColorPainter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage @@ -41,16 +43,12 @@ import dev.datlag.aniflow.common.htmlToAnnotatedString import dev.datlag.aniflow.common.plus import dev.datlag.aniflow.common.toComposeColor import dev.datlag.aniflow.common.toComposeString +import dev.datlag.aniflow.other.Constants import dev.datlag.aniflow.other.StateSaver import dev.datlag.aniflow.ui.navigation.screen.initial.settings.component.* import dev.datlag.tooling.compose.onClick import dev.datlag.tooling.decompose.lifecycle.collectAsStateWithLifecycle -import dev.icerock.moko.resources.compose.stringResource -import io.github.aakira.napier.Napier -import kotlinx.coroutines.flow.update -import dev.datlag.aniflow.settings.model.Color as SettingsColor -import dev.datlag.aniflow.settings.model.TitleLanguage as SettingsTitle -import dev.datlag.aniflow.settings.model.CharLanguage as SettingsChar +import dev.icerock.moko.resources.compose.painterResource @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3WindowSizeClassApi::class, ExperimentalMaterial3Api::class) @Composable @@ -70,6 +68,7 @@ fun SettingsScreen(component: SettingsComponent) { item { UserSection( userFlow = component.user, + loginUri = component.loginUri, modifier = Modifier.fillParentMaxWidth() ) } @@ -106,6 +105,85 @@ fun SettingsScreen(component: SettingsComponent) { modifier = Modifier.fillParentMaxWidth() ) } + item { + val uriHandler = LocalUriHandler.current + val isLoggedIn by component.isLoggedIn.collectAsStateWithLifecycle(false) + + Row( + modifier = Modifier + .fillParentMaxWidth() + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.small) + .onClick { + if (isLoggedIn) { + component.logout() + } else { + uriHandler.openUri(component.loginUri) + } + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + if (isLoggedIn) { + Icon( + imageVector = Icons.Default.NotInterested, + contentDescription = null, + ) + Text(text = "Logout") + } else { + Image( + modifier = Modifier.size(24.dp).clip(CircleShape), + painter = painterResource(SharedRes.images.anilist), + contentDescription = null, + ) + Text(text = "Login") + } + } + } + item { + val uriHandler = LocalUriHandler.current + + Row( + modifier = Modifier + .fillParentMaxWidth() + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.small) + .onClick { + uriHandler.openUri(Constants.GITHUB_REPO) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Image( + modifier = Modifier.size(24.dp), + painter = painterResource(SharedRes.images.github), + contentDescription = null, + colorFilter = ColorFilter.tint(LocalContentColor.current) + ) + Text(text = "GitHub Repository") + } + } + item { + val uriHandler = LocalUriHandler.current + + Row( + modifier = Modifier + .fillParentMaxWidth() + .defaultMinSize(minHeight = ButtonDefaults.MinHeight) + .clip(MaterialTheme.shapes.medium) + .onClick { + uriHandler.openUri(Constants.GITHUB_OWNER) + }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + imageVector = Icons.Default.Code, + contentDescription = null, + ) + Text(text = "Developed by DatLag") + } + } } DisposableEffect(listState) { diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt index 21936f0..dd175d3 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/SettingsScreenComponent.kt @@ -24,6 +24,9 @@ class SettingsScreenComponent( private val userHelper by di.instance() override val user: Flow = userHelper.user.flowOn(ioDispatcher()) + override val isLoggedIn: Flow = userHelper.isLoggedIn.flowOn(ioDispatcher()) + override val loginUri: String = userHelper.loginUrl + override val adultContent: Flow = appSettings.adultContent.flowOn(ioDispatcher()) override val selectedColor: Flow = appSettings.color.flowOn(ioDispatcher()) override val selectedTitleLanguage: Flow = appSettings.titleLanguage.flowOn(ioDispatcher()) @@ -59,4 +62,10 @@ class SettingsScreenComponent( userHelper.updateCharLanguage(value) } } + + override fun logout() { + launchIO { + userHelper.logout() + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/component/UserSection.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/component/UserSection.kt index 0981f0f..8bf7c20 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/component/UserSection.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/navigation/screen/initial/settings/component/UserSection.kt @@ -32,6 +32,7 @@ import org.kodein.di.instance @Composable fun UserSection( userFlow: Flow, + loginUri: String, modifier: Modifier = Modifier, ) { val user by userFlow.collectAsStateWithLifecycle(null) @@ -42,13 +43,12 @@ fun UserSection( horizontalAlignment = Alignment.CenterHorizontally ) { val uriHandler = LocalUriHandler.current - val userHelper by LocalDI.current.instance() AsyncImage( modifier = Modifier.size(96.dp).clip(CircleShape).onClick( enabled = user == null, ) { - uriHandler.openUri(userHelper.loginUrl) + uriHandler.openUri(loginUri) }, model = user?.avatar?.large, contentDescription = null, @@ -65,15 +65,9 @@ fun UserSection( style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold ) - user?.description?.let { - Markdown( - modifier = Modifier.padding(bottom = 16.dp), - content = it - ) - } ?: run { - Text( - text = "Click the image above to login with AniList" - ) - } + Markdown( + modifier = Modifier.padding(bottom = 16.dp), + content = user?.description ?: "Login with [AniList](${loginUri})" + ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt index c29ea5c..93b380d 100644 --- a/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt +++ b/composeApp/src/commonMain/kotlin/dev/datlag/aniflow/ui/theme/SchemeTheme.kt @@ -169,10 +169,7 @@ fun SchemeTheme( content: @Composable (SchemeTheme.Updater?) -> Unit ) { val state = rememberSchemeThemeDominantColorState(key) - val scope = rememberCoroutineScope() - val updater = remember(key, scope) { - key?.let { SchemeTheme.Updater.Default(it, scope) } - } ?: SchemeTheme.create(key) + val updater = SchemeTheme.create(key) DynamicMaterialTheme( seedColor = state?.color, diff --git a/composeApp/src/commonMain/moko-resources/images/github.svg b/composeApp/src/commonMain/moko-resources/images/github.svg new file mode 100644 index 0000000..b837e31 --- /dev/null +++ b/composeApp/src/commonMain/moko-resources/images/github.svg @@ -0,0 +1,3 @@ + + + diff --git a/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/DataStoreUserSettings.kt b/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/DataStoreUserSettings.kt index 539a0ab..1a2a43a 100644 --- a/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/DataStoreUserSettings.kt +++ b/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/DataStoreUserSettings.kt @@ -49,4 +49,15 @@ class DataStoreUserSettings( ) } } + + override suspend fun removeAniListToken() { + dataStore.updateData { + it.copy( + aniList = it.aniList.copy( + accessToken = null, + expires = null + ) + ) + } + } } \ No newline at end of file diff --git a/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/Settings.kt b/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/Settings.kt index 694bd8d..c849a93 100644 --- a/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/Settings.kt +++ b/settings/src/commonMain/kotlin/dev/datlag/aniflow/settings/Settings.kt @@ -14,6 +14,7 @@ data object Settings { access: String, expires: Int? ) + suspend fun removeAniListToken() } interface PlatformAppSettings {