diff --git a/app/src/androidTest/java/xyz/wingio/dimett/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/wingio/dimett/ExampleInstrumentedTest.kt deleted file mode 100644 index a81b9b6..0000000 --- a/app/src/androidTest/java/xyz/wingio/dimett/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.wingio.dimett - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("xyz.wingio.dimett", appContext.packageName) - } -} \ No newline at end of file diff --git a/app/src/main/java/xyz/wingio/dimett/rest/dto/Application.kt b/app/src/main/java/xyz/wingio/dimett/rest/dto/Application.kt index 9e46ff7..2c3007c 100644 --- a/app/src/main/java/xyz/wingio/dimett/rest/dto/Application.kt +++ b/app/src/main/java/xyz/wingio/dimett/rest/dto/Application.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable data class Application( val name: String, val website: String? = null, - @SerialName("vapid_key") val vapidKey: String, + @SerialName("vapid_key") val vapidKey: String? = null, @SerialName("client_id") val clientId: String? = null, @SerialName("client_secret") val clientSecret: String? = null, val id: String? = null diff --git a/app/src/main/java/xyz/wingio/dimett/ui/screens/auth/LoginScreen.kt b/app/src/main/java/xyz/wingio/dimett/ui/screens/auth/LoginScreen.kt index 206d37d..5224819 100644 --- a/app/src/main/java/xyz/wingio/dimett/ui/screens/auth/LoginScreen.kt +++ b/app/src/main/java/xyz/wingio/dimett/ui/screens/auth/LoginScreen.kt @@ -1,9 +1,13 @@ package xyz.wingio.dimett.ui.screens.auth +import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn @@ -47,21 +51,6 @@ class LoginScreen : Screen { viewModel: LoginViewModel = getScreenModel() ) { val navigator = LocalNavigator.currentOrThrow - val ctx = LocalContext.current - var lastTyped by remember { - mutableLongStateOf(0L) - } - - // Only send request when user is done typing - LaunchedEffect(Unit) { - while (true) { - lastTyped += 1 - delay(1.seconds / 30) - if (lastTyped == 23L) { - viewModel.loadDetails() - } - } - } IntentHandler { intent -> viewModel.handleIntent(intent, navigator) @@ -77,49 +66,83 @@ class LoginScreen : Screen { ) { Image(painter = painterResource(R.drawable.ic_app), contentDescription = null) - if (viewModel.nodeInfoLoading) { - CircularProgressIndicator( - modifier = Modifier.size(18.dp), - strokeWidth = 3.dp - ) + if(viewModel.loginLoading) { + CircularProgressIndicator() + } else { + Login(viewModel) } + } + } + } + + @Composable + private fun ColumnScope.Login( + viewModel: LoginViewModel + ) { + val ctx = LocalContext.current + var lastTyped by remember { + mutableLongStateOf(0L) + } - viewModel.nodeInfo?.let { - InstancePreview( - url = viewModel.instance, - nodeInfo = it - ) + // Only send request when user is done typing + LaunchedEffect(Unit) { + while (true) { + lastTyped += 1 + delay(1.seconds / 30) + if (lastTyped == 23L) { + viewModel.loadDetails() } + } + } + + if (viewModel.nodeInfoLoading) { + CircularProgressIndicator( + modifier = Modifier.size(18.dp), + strokeWidth = 3.dp + ) + } - OutlinedTextField( - value = viewModel.instance, - onValueChange = { - viewModel.instance = it - lastTyped = 0L - viewModel.nodeInfo = null - }, - label = { Text(getString(R.string.label_instance)) }, - placeholder = { Text("mastodon.online") }, - isError = viewModel.didError + AnimatedVisibility( + visible = viewModel.nodeInfo != null + ) { + viewModel.nodeInfo?.let { + InstancePreview( + url = viewModel.instance, + nodeInfo = it ) + } + } - if (viewModel.didError) { - Text( - text = getString(R.string.msg_invalid_instance), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.error, - textAlign = TextAlign.Center, - modifier = Modifier.widthIn(max = 300.dp) - ) - } + Spacer(Modifier.height(24.dp)) - Button( - onClick = { viewModel.login(ctx) }, - enabled = viewModel.nodeInfo != null - ) { - Text(getString(R.string.action_login)) - } - } + OutlinedTextField( + value = viewModel.instance, + onValueChange = { + viewModel.instance = it + lastTyped = 0L + viewModel.nodeInfo = null + }, + label = { Text(getString(R.string.label_instance)) }, + placeholder = { Text("mastodon.online") }, + isError = viewModel.didError, + singleLine = true + ) + + if (viewModel.didError) { + Text( + text = getString(R.string.msg_invalid_instance), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error, + textAlign = TextAlign.Center, + modifier = Modifier.widthIn(max = 300.dp) + ) + } + + Button( + onClick = { viewModel.login(ctx) }, + enabled = viewModel.nodeInfo != null && viewModel.instanceIsMastodon + ) { + Text(getString(R.string.action_login)) } } diff --git a/app/src/main/java/xyz/wingio/dimett/ui/viewmodels/auth/LoginViewModel.kt b/app/src/main/java/xyz/wingio/dimett/ui/viewmodels/auth/LoginViewModel.kt index 12018ff..d727622 100644 --- a/app/src/main/java/xyz/wingio/dimett/ui/viewmodels/auth/LoginViewModel.kt +++ b/app/src/main/java/xyz/wingio/dimett/ui/viewmodels/auth/LoginViewModel.kt @@ -34,10 +34,14 @@ class LoginViewModel( var didError by mutableStateOf(false) var nodeInfo by mutableStateOf(null as NodeInfo?) + var loginLoading by mutableStateOf(false) var nodeInfoLoading by mutableStateOf(false) private var instanceUrl: String? = null + val instanceIsMastodon: Boolean + get() = nodeInfo?.metadata?.features?.contains("mastodon_api") == true || nodeInfo?.software?.name == "mastodon" + fun login(context: Context) { if (instance.isEmpty()) return coroutineScope.launch(Dispatchers.IO) { @@ -113,6 +117,7 @@ class LoginViewModel( coroutineScope.launch { val instance = instanceManager[url] ?: return@launch + loginLoading = true mastodonRepository.getToken( instanceUrl = instance.url, clientId = instance.clientId, @@ -132,6 +137,7 @@ class LoginViewModel( } } + loginLoading = false instanceUrl = null // Don't let duplicate intents happen } } diff --git a/app/src/main/java/xyz/wingio/dimett/ui/widgets/auth/InstancePreview.kt b/app/src/main/java/xyz/wingio/dimett/ui/widgets/auth/InstancePreview.kt index fe71d87..cbb6d81 100644 --- a/app/src/main/java/xyz/wingio/dimett/ui/widgets/auth/InstancePreview.kt +++ b/app/src/main/java/xyz/wingio/dimett/ui/widgets/auth/InstancePreview.kt @@ -2,8 +2,12 @@ package xyz.wingio.dimett.ui.widgets.auth import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -27,6 +31,7 @@ fun InstancePreview( url: String, nodeInfo: NodeInfo ) { + val iconSize = 56.dp val name = nodeInfo.metadata?.nodeName ?: url val icon = when (nodeInfo.software.name) { @@ -36,24 +41,25 @@ fun InstancePreview( else -> painterResource(R.drawable.img_logo_fediverse) } - ElevatedCard( - modifier = Modifier.width(350.dp) + Box( + modifier = Modifier + .width(350.dp) + .padding(bottom = iconSize / 2) ) { - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(16.dp) + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .offset(y = iconSize / 2) ) { - Image( - painter = icon, - contentDescription = nodeInfo.software.name, - modifier = Modifier - .size(55.dp) - .shadow(2.dp, CircleShape) - ) Column( - verticalArrangement = Arrangement.spacedBy(3.dp) + verticalArrangement = Arrangement.spacedBy(3.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) ) { + Spacer(Modifier.height(16.dp)) + Text( text = name, style = MaterialTheme.typography.bodyLarge, @@ -82,5 +88,14 @@ fun InstancePreview( } } + Image( + icon, + contentDescription = null, + modifier = Modifier + .padding(horizontal = 16.dp) + .size(iconSize) + .align(Alignment.TopCenter) + .shadow(10.dp, CircleShape) + ) } } \ No newline at end of file diff --git a/app/src/test/java/xyz/wingio/dimett/ExampleUnitTest.kt b/app/src/test/java/xyz/wingio/dimett/ExampleUnitTest.kt deleted file mode 100644 index 5a2e9ad..0000000 --- a/app/src/test/java/xyz/wingio/dimett/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.wingio.dimett - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file