diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index bf5ba9b60..d7cb58b81 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -5,12 +5,16 @@
+
+
+
+
@@ -26,9 +30,11 @@
+
+
\ No newline at end of file
diff --git a/android-core/src/main/kotlin/co/anitrend/core/android/extensions/ComposeExtensions.kt b/android-core/src/main/kotlin/co/anitrend/core/android/extensions/ComposeExtensions.kt
new file mode 100644
index 000000000..b8dbac167
--- /dev/null
+++ b/android-core/src/main/kotlin/co/anitrend/core/android/extensions/ComposeExtensions.kt
@@ -0,0 +1,36 @@
+package co.anitrend.core.android.extensions
+
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.os.Build
+import androidx.annotation.DrawableRes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.graphics.drawable.toBitmap
+
+/**
+ * [Source](https://gist.github.com/tkuenneth/ddf598663f041dc79960cda503d14448?permalink_comment_id=4660486#gistcomment-4660486)
+ */
+@Composable
+fun adaptiveIconPainterResource(@DrawableRes id: Int): Painter {
+ val res = LocalContext.current.resources
+ val theme = LocalContext.current.theme
+
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // Android O supports adaptive icons, try loading this first (even though this is least likely to be the format).
+ val adaptiveIcon = ResourcesCompat.getDrawable(res, id, theme) as? AdaptiveIconDrawable
+ if (adaptiveIcon != null) {
+ BitmapPainter(adaptiveIcon.toBitmap().asImageBitmap())
+ } else {
+ // We couldn't load the drawable as an Adaptive Icon, just use painterResource
+ painterResource(id)
+ }
+ } else {
+ // We're not on Android O or later, just use painterResource
+ painterResource(id)
+ }
+}
diff --git a/app-navigation/src/main/kotlin/co/anitrend/navigation/model/common/IParam.kt b/app-navigation/src/main/kotlin/co/anitrend/navigation/model/common/IParam.kt
index f5c37985d..8014241c9 100644
--- a/app-navigation/src/main/kotlin/co/anitrend/navigation/model/common/IParam.kt
+++ b/app-navigation/src/main/kotlin/co/anitrend/navigation/model/common/IParam.kt
@@ -18,8 +18,12 @@
package co.anitrend.navigation.model.common
import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
/**
* Router parameter contract
*/
interface IParam : Parcelable
+
+ @Parcelize
+ data class None(override val idKey: String = "") : IParam
\ No newline at end of file
diff --git a/feature-auth/src/main/AndroidManifest.xml b/feature-auth/src/main/AndroidManifest.xml
index f6c218941..24ae131e0 100644
--- a/feature-auth/src/main/AndroidManifest.xml
+++ b/feature-auth/src/main/AndroidManifest.xml
@@ -24,7 +24,6 @@
-
diff --git a/feature-auth/src/main/kotlin/co/anitrend/auth/component/compose/AuthScreenContent.kt b/feature-auth/src/main/kotlin/co/anitrend/auth/component/compose/AuthScreenContent.kt
new file mode 100644
index 000000000..eb07df814
--- /dev/null
+++ b/feature-auth/src/main/kotlin/co/anitrend/auth/component/compose/AuthScreenContent.kt
@@ -0,0 +1,196 @@
+package co.anitrend.auth.component.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.rounded.ArrowBack
+import androidx.compose.material.icons.rounded.AccountCircle
+import androidx.compose.material.icons.twotone.Info
+import androidx.compose.material.icons.twotone.NoAccounts
+import androidx.compose.material3.BottomAppBar
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Snackbar
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import co.anitrend.auth.component.viewmodel.state.AuthState
+import co.anitrend.core.android.ui.AniTrendPreview
+import co.anitrend.core.android.ui.theme.AniTrendTheme3
+import co.anitrend.core.android.ui.typography.AniTrendTypography
+import kotlinx.coroutines.launch
+
+@Composable
+private fun AuthBrandNameComponent(modifier: Modifier = Modifier) {
+ Row(modifier = modifier) {
+ Text(
+ text = stringResource(co.anitrend.auth.R.string.auth_label_segment_first),
+ style = AniTrendTypography.displayMedium,
+ )
+ Text(
+ text = stringResource(co.anitrend.auth.R.string.auth_label_segment_second),
+ style = AniTrendTypography.displayMedium.copy(
+ color = colorResource(co.anitrend.arch.theme.R.color.colorStateBlue)
+ ),
+
+ )
+ }
+}
+
+@Composable
+private fun AuthHeaderSection(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier.padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ AuthBrandNameComponent()
+ Spacer(modifier = Modifier.padding(top = 8.dp))
+ Text(text = stringResource(co.anitrend.auth.R.string.label_allow_authorization))
+ }
+}
+
+@Composable
+private fun AuthAuthorizationSection(
+ onAuthorizeClick: () -> Unit,
+ onAuthorizationHelpClick: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier.padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ FilledTonalButton(
+ onClick = onAuthorizeClick,
+ ) {
+ Icon(imageVector = Icons.Rounded.AccountCircle, contentDescription = null)
+ Spacer(modifier = Modifier.padding(start = 6.dp))
+ Text(text = stringResource(co.anitrend.auth.R.string.auth_label_authorize))
+ }
+ Spacer(modifier = Modifier.padding(top = 8.dp))
+ SuggestionChip(
+ onClick = onAuthorizationHelpClick,
+ label = {
+ Text(text = stringResource(co.anitrend.auth.R.string.auth_label_having_trouble_logging_in))
+ },
+ icon = { Icon(imageVector = Icons.TwoTone.Info, contentDescription = null) }
+ )
+ }
+}
+
+@Composable
+private fun AuthAnonymousSection(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier.padding(top = 16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = stringResource(co.anitrend.auth.R.string.auth_label_alternative_account),
+ textAlign = TextAlign.Center,
+ style = AniTrendTypography.labelMedium,
+ )
+ Spacer(modifier = Modifier.padding(top = 8.dp))
+ SuggestionChip(
+ onClick = {},
+ label = {
+ Text(text = stringResource(co.anitrend.auth.R.string.auth_label_action_start_anonymous_account))
+ },
+ icon = { Icon(imageVector = Icons.TwoTone.NoAccounts, contentDescription = null) }
+ )
+ }
+}
+
+@Composable
+private fun AuthContent(
+ onAuthorizeClick: () -> Unit,
+ onAuthorizationHelpClick: () -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = modifier.then(Modifier.padding(start = 48.dp, end = 48.dp)),
+ ) {
+ AuthHeaderSection()
+ Spacer(modifier = Modifier.padding(top = 24.dp))
+ AuthAuthorizationSection(onAuthorizeClick, onAuthorizationHelpClick)
+ Spacer(modifier = Modifier.padding(top = 24.dp))
+ AuthAnonymousSection()
+ }
+}
+
+@Composable
+private fun AuthBottomAppBar(onBackPress: () -> Unit) {
+ BottomAppBar(
+ actions = {
+ IconButton(onClick = onBackPress) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
+ contentDescription = "Back"
+ )
+ }
+ }
+ )
+}
+
+@Composable
+fun AuthScreenContent(
+ authState: AuthState,
+ onAuthorizeClick: () -> Unit,
+ onAuthorizationHelpClick: () -> Unit,
+ onBackPress: () -> Unit
+) {
+ val snackbarHostState = remember { SnackbarHostState() }
+ val state = authState.model.observeAsState()
+
+ Scaffold(
+ bottomBar = {
+ AuthBottomAppBar(onBackPress = onBackPress)
+ },
+ snackbarHost = {
+ SnackbarHost(hostState = snackbarHostState)
+ },
+ modifier = Modifier
+ ) { innerPadding ->
+ AuthContent(
+ onAuthorizeClick,
+ onAuthorizationHelpClick,
+ modifier = Modifier.padding(innerPadding)
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ )
+ }
+}
+
+
+@AniTrendPreview.Mobile
+@AniTrendPreview.Light
+@AniTrendPreview.Dark
+@Composable
+private fun MediaDetailComponentPreview() {
+ AniTrendTheme3 {
+ AuthContent(
+ onAuthorizeClick = {},
+ onAuthorizationHelpClick = {}
+ )
+ }
+}
diff --git a/feature-auth/src/main/kotlin/co/anitrend/auth/component/content/AuthContent.kt b/feature-auth/src/main/kotlin/co/anitrend/auth/component/content/AuthContent.kt
index 0e2fb27b2..f60b47d39 100644
--- a/feature-auth/src/main/kotlin/co/anitrend/auth/component/content/AuthContent.kt
+++ b/feature-auth/src/main/kotlin/co/anitrend/auth/component/content/AuthContent.kt
@@ -131,6 +131,7 @@ class AuthContent(
viewModelState().model.observeOnce(viewLifecycleOwner) { user ->
runCatching {
presenter.runSignInWorker(user.id)
+ closeScreen()
}.onFailure {
Snackbar.make(
requireView(),
diff --git a/feature-auth/src/main/kotlin/co/anitrend/auth/component/screen/AuthScreen.kt b/feature-auth/src/main/kotlin/co/anitrend/auth/component/screen/AuthScreen.kt
index 31061fd50..487ba3c79 100644
--- a/feature-auth/src/main/kotlin/co/anitrend/auth/component/screen/AuthScreen.kt
+++ b/feature-auth/src/main/kotlin/co/anitrend/auth/component/screen/AuthScreen.kt
@@ -19,24 +19,37 @@ package co.anitrend.auth.component.screen
import android.content.Intent
import android.os.Bundle
+import android.widget.Toast
+import androidx.activity.compose.setContent
+import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.viewbinding.ViewBinding
import co.anitrend.arch.extension.ext.extra
+import co.anitrend.auth.R
+import co.anitrend.auth.component.compose.AuthScreenContent
import co.anitrend.auth.component.viewmodel.AuthViewModel
-import co.anitrend.auth.databinding.AuthScreenBinding
-import co.anitrend.core.component.screen.AniTrendBoundScreen
-import co.anitrend.core.ui.commit
-import co.anitrend.core.ui.model.FragmentItem
+import co.anitrend.auth.presenter.AuthPresenter
+import co.anitrend.core.android.extensions.observeOnce
+import co.anitrend.core.android.extensions.requireLifecycleOwner
+import co.anitrend.core.android.ui.theme.AniTrendTheme3
+import co.anitrend.core.component.screen.AniTrendScreen
+import co.anitrend.core.ui.inject
import co.anitrend.navigation.AuthRouter
import co.anitrend.navigation.MainRouter
import co.anitrend.navigation.extensions.forActivity
+import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
+import timber.log.Timber
-class AuthScreen : AniTrendBoundScreen() {
+class AuthScreen : AniTrendScreen() {
private val viewModel by viewModel()
private val authRouterParam by extra()
+ private val presenter by inject()
+
private fun checkIntentData() {
viewModel.onIntentData(
applicationContext,
@@ -46,8 +59,29 @@ class AuthScreen : AniTrendBoundScreen() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = AuthScreenBinding.inflate(layoutInflater)
- setContentView(requireBinding().root)
+ setContent {
+ AniTrendTheme3 {
+ //ContentWrapper(
+ // stateFlow = viewModelState().combinedLoadState,
+ // param = IParam.None(),
+ // onLoad = {
+ // viewModelState().invoke(Authentication.Authenticating("", "", 0L))
+ // },
+ // onClick = viewModelState()::retry,
+ //) {
+ AuthScreenContent(
+ authState = viewModelState(),
+ onAuthorizeClick = {
+ presenter.authorizeWithAniList(this)
+ },
+ onAuthorizationHelpClick = {
+ presenter.authorizationIssues(this)
+ },
+ onBackPress = ::onBackPressed
+ )
+ //}
+ }
+ }
}
/**
@@ -57,11 +91,21 @@ class AuthScreen : AniTrendBoundScreen() {
* @param savedInstanceState
*/
override fun initializeComponents(savedInstanceState: Bundle?) {
- lifecycleScope.launch {
- onUpdateUserInterface()
+ viewModelState().model.observeOnce(requireLifecycleOwner()) { user ->
+ runCatching {
+ presenter.runSignInWorker(user.id)
+ }.onSuccess {
+ onBackPressed()
+ }.onFailure {
+ Timber.e(it)
+ Toast.makeText(applicationContext, R.string.auth_failed_message, Toast.LENGTH_LONG).show()
+ presenter.runSignOutWorker()
+ }
}
}
+
+
/**
* {@inheritDoc}
*
@@ -97,8 +141,8 @@ class AuthScreen : AniTrendBoundScreen() {
)
}
- private fun onUpdateUserInterface() {
- currentFragmentTag = FragmentItem(AuthRouter.forFragment())
- .commit(requireBinding().splashFrame, this)
- }
+ /**
+ * Proxy for a view model state if one exists
+ */
+ override fun viewModelState() = viewModel.state
}
diff --git a/feature-auth/src/main/kotlin/co/anitrend/auth/koin/Modules.kt b/feature-auth/src/main/kotlin/co/anitrend/auth/koin/Modules.kt
index 8b4a6e2ee..98f301095 100644
--- a/feature-auth/src/main/kotlin/co/anitrend/auth/koin/Modules.kt
+++ b/feature-auth/src/main/kotlin/co/anitrend/auth/koin/Modules.kt
@@ -56,7 +56,7 @@ private val viewModelModule = module {
}
private val presenterModule = module {
- scope {
+ scope {
scoped {
AuthPresenter(
context = androidContext(),
diff --git a/feature-auth/src/main/kotlin/co/anitrend/auth/model/Authentication.kt b/feature-auth/src/main/kotlin/co/anitrend/auth/model/Authentication.kt
index b1dc393ce..d37eb3cfd 100644
--- a/feature-auth/src/main/kotlin/co/anitrend/auth/model/Authentication.kt
+++ b/feature-auth/src/main/kotlin/co/anitrend/auth/model/Authentication.kt
@@ -28,6 +28,6 @@ sealed class Authentication {
val title: String,
val message: String
): Authentication()
-
- object Idle : Authentication()
-}
\ No newline at end of file
+
+ data object Idle : Authentication()
+}