From e316c0bf3a0d951b1aa9ae20adc19e3cf549b7dc Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 11 May 2023 00:20:40 +0200 Subject: [PATCH] feat: registration flow Signed-off-by: Infi --- app/build.gradle | 3 + app/src/main/AndroidManifest.xml | 2 +- .../revolt/{ => activities}/MainActivity.kt | 22 +- .../revolt/api/routes/account/Register.kt | 39 ++++ .../revolt/components/chat/MessageField.kt | 4 +- .../chat/revolt/components/generic/Weblink.kt | 7 +- .../screens/chat/TypingIndicator.kt | 4 +- .../revolt/screens/about/PlaceholderScreen.kt | 7 +- .../chat/views/channel/ChannelScreen.kt | 8 +- ...reeterScreen.kt => LoginGreetingScreen.kt} | 79 +++++-- .../chat/revolt/screens/login/LoginScreen.kt | 58 +++-- .../chat/revolt/screens/login/MfaScreen.kt | 38 ++- .../screens/register/RegisterDetailsScreen.kt | 220 ++++++++++++++++++ .../register/RegisterGreetingScreen.kt | 102 ++++++++ .../screens/register/RegisterVerifyScreen.kt | 103 ++++++++ .../main/java/chat/revolt/ui/theme/Theme.kt | 2 + .../main/res/drawable/revolt_onboarding.xml | 27 +++ app/src/main/res/values/strings.xml | 10 +- build.gradle | 4 +- 19 files changed, 650 insertions(+), 89 deletions(-) rename app/src/main/java/chat/revolt/{ => activities}/MainActivity.kt (85%) create mode 100644 app/src/main/java/chat/revolt/api/routes/account/Register.kt rename app/src/main/java/chat/revolt/screens/login/{GreeterScreen.kt => LoginGreetingScreen.kt} (65%) create mode 100644 app/src/main/java/chat/revolt/screens/register/RegisterDetailsScreen.kt create mode 100644 app/src/main/java/chat/revolt/screens/register/RegisterGreetingScreen.kt create mode 100644 app/src/main/java/chat/revolt/screens/register/RegisterVerifyScreen.kt create mode 100644 app/src/main/res/drawable/revolt_onboarding.xml diff --git a/app/build.gradle b/app/build.gradle index 29e1adf5..b21fa65a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -178,6 +178,9 @@ dependencies { implementation 'com.github.MikeOrtiz:TouchImageView:3.3' implementation "androidx.appcompat:appcompat:1.7.0-alpha02" + // hCaptcha - captcha provider + implementation "com.github.hcaptcha:hcaptcha-android-sdk:3.8.1" + // JDK Desugaring - polyfill for new Java APIs coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aad60517..4ff80eef 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,7 @@ android:value="false" /> diff --git a/app/src/main/java/chat/revolt/MainActivity.kt b/app/src/main/java/chat/revolt/activities/MainActivity.kt similarity index 85% rename from app/src/main/java/chat/revolt/MainActivity.kt rename to app/src/main/java/chat/revolt/activities/MainActivity.kt index d6eec214..e24ca02f 100644 --- a/app/src/main/java/chat/revolt/MainActivity.kt +++ b/app/src/main/java/chat/revolt/activities/MainActivity.kt @@ -1,7 +1,6 @@ -package chat.revolt +package chat.revolt.activities import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.animation.* import androidx.compose.animation.core.EaseInOutExpo @@ -14,7 +13,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize +import androidx.fragment.app.FragmentActivity import androidx.navigation.compose.dialog +import chat.revolt.BuildConfig import chat.revolt.api.settings.GlobalState import chat.revolt.screens.SplashScreen import chat.revolt.screens.about.AboutScreen @@ -22,9 +23,12 @@ import chat.revolt.screens.about.AttributionScreen import chat.revolt.screens.about.PlaceholderScreen import chat.revolt.screens.chat.ChatRouterScreen import chat.revolt.screens.chat.dialogs.FeedbackDialog -import chat.revolt.screens.login.GreeterScreen +import chat.revolt.screens.login.LoginGreetingScreen import chat.revolt.screens.login.LoginScreen import chat.revolt.screens.login.MfaScreen +import chat.revolt.screens.register.RegisterDetailsScreen +import chat.revolt.screens.register.RegisterGreetingScreen +import chat.revolt.screens.register.RegisterVerifyScreen import chat.revolt.screens.settings.AppearanceSettingsScreen import chat.revolt.screens.settings.DebugSettingsScreen import chat.revolt.screens.settings.SettingsScreen @@ -36,7 +40,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.sentry.android.core.SentryAndroid @AndroidEntryPoint -class MainActivity : ComponentActivity() { +class MainActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -98,7 +102,7 @@ fun AppEntrypoint() { ) { composable("splash") { SplashScreen(navController) } - composable("login/greeting") { GreeterScreen(navController) } + composable("login/greeting") { LoginGreetingScreen(navController) } composable("login/login") { LoginScreen(navController) } composable("login/mfa/{mfaTicket}/{allowedAuthTypes}") { backStackEntry -> val mfaTicket = backStackEntry.arguments?.getString("mfaTicket") ?: "" @@ -108,6 +112,14 @@ fun AppEntrypoint() { MfaScreen(navController, allowedAuthTypes, mfaTicket) } + composable("register/greeting") { RegisterGreetingScreen(navController) } + composable("register/details") { RegisterDetailsScreen(navController) } + composable("register/verify/{email}") { backStackEntry -> + val email = backStackEntry.arguments?.getString("email") ?: "" + + RegisterVerifyScreen(navController, email) + } + composable("chat") { ChatRouterScreen(navController) } composable("settings") { SettingsScreen(navController) } diff --git a/app/src/main/java/chat/revolt/api/routes/account/Register.kt b/app/src/main/java/chat/revolt/api/routes/account/Register.kt new file mode 100644 index 00000000..22e6a892 --- /dev/null +++ b/app/src/main/java/chat/revolt/api/routes/account/Register.kt @@ -0,0 +1,39 @@ +package chat.revolt.api.routes.account + +import chat.revolt.api.RevoltError +import chat.revolt.api.RevoltHttp +import chat.revolt.api.RevoltJson +import chat.revolt.api.schemas.RsResult +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import io.ktor.client.statement.bodyAsText +import io.ktor.http.ContentType +import io.ktor.http.contentType +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException + +@Serializable +data class RegistrationBody( + val email: String, + val password: String, + val invite: String? = null, + val captcha: String, +) + +suspend fun register(body: RegistrationBody): RsResult { + val response = RevoltHttp.post("/auth/account/create") { + setBody(body) + contentType(ContentType.Application.Json) + } + + val responseContent = response.bodyAsText() + + try { + val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) + return RsResult.err(error) + } catch (e: SerializationException) { + // Not an error + } + + return RsResult.ok(Unit) +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/components/chat/MessageField.kt b/app/src/main/java/chat/revolt/components/chat/MessageField.kt index ff9117b6..6861a0df 100644 --- a/app/src/main/java/chat/revolt/components/chat/MessageField.kt +++ b/app/src/main/java/chat/revolt/components/chat/MessageField.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.revolt.R -import chat.revolt.RevoltTweenFloat -import chat.revolt.RevoltTweenInt +import chat.revolt.activities.RevoltTweenFloat +import chat.revolt.activities.RevoltTweenInt import chat.revolt.api.schemas.ChannelType @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/chat/revolt/components/generic/Weblink.kt b/app/src/main/java/chat/revolt/components/generic/Weblink.kt index bae29b38..293ed971 100644 --- a/app/src/main/java/chat/revolt/components/generic/Weblink.kt +++ b/app/src/main/java/chat/revolt/components/generic/Weblink.kt @@ -4,6 +4,7 @@ import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.padding +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -36,14 +37,16 @@ fun AnyLink(text: String, action: () -> Unit, modifier: Modifier = Modifier) { color = MaterialTheme.colorScheme.onBackground.copy( alpha = 0.5f ), - style = MaterialTheme.typography.titleMedium.copy( + style = LocalTextStyle.current.copy( textAlign = TextAlign.Center, fontWeight = FontWeight.Normal, fontSize = 15.sp ), modifier = modifier .padding(horizontal = 2.5.dp, vertical = 3.dp) - .clickable(onClick = action) + .clickable( + onClick = action + ) ) } diff --git a/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt b/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt index 96a9042a..58d3f290 100644 --- a/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt +++ b/app/src/main/java/chat/revolt/components/screens/chat/TypingIndicator.kt @@ -14,8 +14,8 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.revolt.R -import chat.revolt.RevoltTweenFloat -import chat.revolt.RevoltTweenInt +import chat.revolt.activities.RevoltTweenFloat +import chat.revolt.activities.RevoltTweenInt import chat.revolt.api.RevoltAPI import chat.revolt.components.generic.UserAvatar diff --git a/app/src/main/java/chat/revolt/screens/about/PlaceholderScreen.kt b/app/src/main/java/chat/revolt/screens/about/PlaceholderScreen.kt index 460b2a54..0ce52f18 100644 --- a/app/src/main/java/chat/revolt/screens/about/PlaceholderScreen.kt +++ b/app/src/main/java/chat/revolt/screens/about/PlaceholderScreen.kt @@ -1,6 +1,10 @@ package chat.revolt.screens.about -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -51,6 +55,7 @@ fun PlaceholderScreen( color = MaterialTheme.colorScheme.onBackground.copy( alpha = 0.5f ), + textAlign = TextAlign.Center, modifier = Modifier .padding(horizontal = 20.dp, vertical = 10.dp) .fillMaxWidth() diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index 0252a18a..5c6edd1f 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -28,9 +28,9 @@ import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import chat.revolt.R -import chat.revolt.RevoltTweenDp -import chat.revolt.RevoltTweenFloat -import chat.revolt.RevoltTweenInt +import chat.revolt.activities.RevoltTweenDp +import chat.revolt.activities.RevoltTweenFloat +import chat.revolt.activities.RevoltTweenInt import chat.revolt.api.RevoltAPI import chat.revolt.api.routes.microservices.autumn.FileArgs import chat.revolt.components.chat.Message @@ -215,7 +215,7 @@ fun ChannelScreen( memberMap = mapOf(), userMap = RevoltAPI.userCache.toMap(), channelMap = RevoltAPI.channelCache.mapValues { ch -> - ch.value.name ?: ch.value.id!! + ch.value.name ?: ch.value.id ?: "#DeletedChannel" }, emojiMap = RevoltAPI.emojiCache, serverId = channel.server ?: "", diff --git a/app/src/main/java/chat/revolt/screens/login/GreeterScreen.kt b/app/src/main/java/chat/revolt/screens/login/LoginGreetingScreen.kt similarity index 65% rename from app/src/main/java/chat/revolt/screens/login/GreeterScreen.kt rename to app/src/main/java/chat/revolt/screens/login/LoginGreetingScreen.kt index 7d6791fa..ee2c4525 100644 --- a/app/src/main/java/chat/revolt/screens/login/GreeterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/login/LoginGreetingScreen.kt @@ -4,12 +4,25 @@ import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -23,23 +36,23 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import chat.revolt.R +import chat.revolt.api.REVOLT_MARKETING +import chat.revolt.components.generic.Weblink @Composable -fun GreeterScreen(navController: NavController) { +fun LoginGreetingScreen(navController: NavController) { val context = LocalContext.current var catTaps by remember { mutableStateOf(0) } Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), + .fillMaxSize() + .padding(vertical = 20.dp, horizontal = 0.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() .weight(1f), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally @@ -49,8 +62,8 @@ fun GreeterScreen(navController: NavController) { contentDescription = "Revolt Logo", contentScale = ContentScale.Fit, modifier = Modifier - .height(60.dp) - .padding(bottom = 30.dp) + .height(70.dp) + .padding(bottom = 10.dp) .clickable( interactionSource = remember(::MutableInteractionSource), indication = null @@ -73,7 +86,7 @@ fun GreeterScreen(navController: NavController) { Text( text = stringResource(R.string.login_onboarding_heading), style = MaterialTheme.typography.displaySmall.copy( - fontSize = 30.sp, + fontSize = 26.sp, fontWeight = FontWeight.Black, textAlign = TextAlign.Center ), @@ -88,6 +101,7 @@ fun GreeterScreen(navController: NavController) { alpha = 0.5f ), style = MaterialTheme.typography.titleMedium.copy( + fontSize = 16.sp, textAlign = TextAlign.Center, fontWeight = FontWeight.Normal, ), @@ -99,18 +113,10 @@ fun GreeterScreen(navController: NavController) { Column( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 30.dp) + .width(200.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally ) { - ElevatedButton( - onClick = { navController.navigate("about/placeholder") }, - modifier = Modifier - .fillMaxWidth() - .testTag("view_signup_page_button") - ) { - Text(text = stringResource(R.string.signup)) - } - Button( onClick = { navController.navigate("login/login") }, modifier = Modifier @@ -119,6 +125,37 @@ fun GreeterScreen(navController: NavController) { ) { Text(text = stringResource(R.string.login)) } + + Spacer(modifier = Modifier.height(5.dp)) + + ElevatedButton( + onClick = { navController.navigate("register/greeting") }, + modifier = Modifier + .fillMaxWidth() + .testTag("view_signup_page_button") + ) { + Text(text = stringResource(R.string.signup)) + } + + Spacer(modifier = Modifier.height(40.dp)) + + CompositionLocalProvider( + LocalTextStyle provides LocalTextStyle.current.copy(textAlign = TextAlign.Center) + ) { + Weblink( + text = stringResource(R.string.terms_of_service), + url = "$REVOLT_MARKETING/terms" + ) + Weblink( + text = stringResource(R.string.privacy_policy), + url = "$REVOLT_MARKETING/privacy" + ) + Weblink( + text = stringResource(R.string.community_guidelines), + url = "$REVOLT_MARKETING/aup" + ) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/login/LoginScreen.kt b/app/src/main/java/chat/revolt/screens/login/LoginScreen.kt index 1d55b397..35a4e76e 100644 --- a/app/src/main/java/chat/revolt/screens/login/LoginScreen.kt +++ b/app/src/main/java/chat/revolt/screens/login/LoginScreen.kt @@ -1,12 +1,24 @@ package chat.revolt.screens.login import android.util.Log -import androidx.compose.foundation.layout.* +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material3.Button -import androidx.compose.material3.ElevatedButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -118,6 +130,7 @@ fun LoginScreen( }" ) } + "home" -> { navController.navigate("chat") { popUpTo("login/greeting") { inclusive = true } @@ -131,15 +144,13 @@ fun LoginScreen( Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), + .fillMaxSize() + .padding(20.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() .weight(1f), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally @@ -199,8 +210,7 @@ fun LoginScreen( Column( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 30.dp), + .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally ) { Weblink( @@ -217,22 +227,22 @@ fun LoginScreen( .testTag("resend_verification_link") ) - ElevatedButton( - onClick = { navController.popBackStack() }, - modifier = Modifier - .fillMaxWidth() - .testTag("exit_login_page_button") - ) { - Text(text = stringResource(R.string.back)) - } + Spacer(modifier = Modifier.height(10.dp)) - Button( - onClick = { viewModel.doLogin() }, - modifier = Modifier - .fillMaxWidth() - .testTag("do_login_button") - ) { - Text(text = stringResource(R.string.login)) + Row { + TextButton(onClick = { + navController.popBackStack() + }) { + Text(text = stringResource(R.string.back)) + } + + Spacer(modifier = Modifier.width(10.dp)) + + Button(onClick = { + viewModel.doLogin() + }) { + Text(text = stringResource(R.string.login)) + } } } } diff --git a/app/src/main/java/chat/revolt/screens/login/MfaScreen.kt b/app/src/main/java/chat/revolt/screens/login/MfaScreen.kt index 0a27b128..78be485b 100644 --- a/app/src/main/java/chat/revolt/screens/login/MfaScreen.kt +++ b/app/src/main/java/chat/revolt/screens/login/MfaScreen.kt @@ -1,14 +1,22 @@ package chat.revolt.screens.login import android.util.Log -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ElevatedButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -132,23 +140,19 @@ fun MfaScreen( Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight(), + .fillMaxSize() + .padding(20.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Column( modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() .weight(1f) - .verticalScroll( - rememberScrollState() - ), + .fillMaxWidth() + .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Text( text = stringResource(R.string.mfa_interstitial_header), style = MaterialTheme.typography.displaySmall.copy( @@ -235,6 +239,7 @@ fun MfaScreen( } } } + "Recovery" -> { CollapsibleCard(title = stringResource(R.string.mfa_recovery_header)) { Column( @@ -279,17 +284,10 @@ fun MfaScreen( } } - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 30.dp) + ElevatedButton( + onClick = { navController.popBackStack() } ) { - ElevatedButton( - onClick = { navController.popBackStack() }, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(R.string.cancel)) - } + Text(text = stringResource(R.string.cancel)) } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/register/RegisterDetailsScreen.kt b/app/src/main/java/chat/revolt/screens/register/RegisterDetailsScreen.kt new file mode 100644 index 00000000..b765ef66 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/register/RegisterDetailsScreen.kt @@ -0,0 +1,220 @@ +package chat.revolt.screens.register + +import android.content.Context +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import chat.revolt.R +import chat.revolt.api.routes.account.RegistrationBody +import chat.revolt.api.routes.account.register +import chat.revolt.api.routes.misc.getRootRoute +import chat.revolt.components.generic.FormTextField +import com.hcaptcha.sdk.HCaptcha +import com.hcaptcha.sdk.HCaptchaConfig +import com.hcaptcha.sdk.HCaptchaSize +import com.hcaptcha.sdk.HCaptchaTheme +import kotlinx.coroutines.launch + +class RegisterDetailsScreenViewModel : ViewModel() { + var email by mutableStateOf("") + var password by mutableStateOf("") + var error by mutableStateOf(null) + private var captchaToken by mutableStateOf(null) + + fun initCaptcha(context: Context, onSuccess: () -> Unit) { + viewModelScope.launch { + val root = getRootRoute() + + if (!root.features.captcha.enabled) { + onSuccess() + return@launch + } + + val config = HCaptchaConfig.builder().apply { + siteKey(root.features.captcha.key) + theme(HCaptchaTheme.DARK) + size(HCaptchaSize.INVISIBLE) + }.build() + + HCaptcha.getClient(context).apply { + addOnSuccessListener { + captchaToken = it.tokenResult + onSuccess() + } + + addOnFailureListener { + error = it.message + } + + setup(config) + verifyWithHCaptcha() + } + } + } + + fun doRegistration(navController: NavController) { + val body = RegistrationBody( + email = email, + password = password, + captcha = captchaToken ?: "" + ) + + viewModelScope.launch { + val result = register(body) + + if (result.ok) { + navController.navigate("register/verify/${email}") + } else { + error = result.unwrapError().type + } + } + } +} + +@Composable +fun RegisterDetailsScreen( + navController: NavController, + viewModel: RegisterDetailsScreenViewModel = viewModel() +) { + val context = LocalContext.current + + Column( + modifier = Modifier + .fillMaxSize() + .padding(20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier + .weight(1f), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.register_form_heading), + style = MaterialTheme.typography.displaySmall.copy( + fontSize = 30.sp, + fontWeight = FontWeight.Black, + textAlign = TextAlign.Center + ), + modifier = Modifier + .padding(horizontal = 10.dp) + .fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = stringResource(R.string.register_data), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + style = MaterialTheme.typography.titleMedium.copy( + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + ), + modifier = Modifier + .padding(horizontal = 10.dp) + .fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(40.dp)) + + Column( + modifier = Modifier + .fillMaxWidth(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + FormTextField( + value = viewModel.email, + onChange = { viewModel.email = it }, + label = stringResource(R.string.register_email), + ) + Text( + text = stringResource(R.string.register_email_verification_hint), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + fontSize = 12.sp, + modifier = Modifier.padding(horizontal = 40.dp, vertical = 10.dp) + ) + + Spacer(modifier = Modifier.height(10.dp)) + + FormTextField( + value = viewModel.password, + onChange = { viewModel.password = it }, + label = stringResource(R.string.register_password), + type = KeyboardType.Password + ) + Text( + text = stringResource(R.string.register_password_rules), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + fontSize = 12.sp, + modifier = Modifier.padding(horizontal = 40.dp, vertical = 10.dp) + ) + + if (!viewModel.error.isNullOrBlank()) { + Text( + text = viewModel.error!!, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(horizontal = 40.dp, vertical = 10.dp), + textAlign = TextAlign.Center + ) + } + } + } + + Row { + TextButton(onClick = { + navController.popBackStack() + }) { + Text(text = stringResource(R.string.back)) + } + + Spacer(modifier = Modifier.width(10.dp)) + + Button( + onClick = { + viewModel.initCaptcha(context) { + viewModel.doRegistration(navController) + } + }, + enabled = viewModel.email.isNotBlank() && viewModel.password.isNotBlank() + ) { + Text(text = stringResource(R.string.signup)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/register/RegisterGreetingScreen.kt b/app/src/main/java/chat/revolt/screens/register/RegisterGreetingScreen.kt new file mode 100644 index 00000000..aae24ddc --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/register/RegisterGreetingScreen.kt @@ -0,0 +1,102 @@ +package chat.revolt.screens.register + +import androidx.compose.foundation.Image +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import chat.revolt.R + +@Composable +fun RegisterGreetingScreen(navController: NavController) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier + .weight(1f), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.register_heading), + style = MaterialTheme.typography.displaySmall.copy( + fontSize = 30.sp, + fontWeight = FontWeight.Black, + textAlign = TextAlign.Center + ), + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 10.dp) + .fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Image( + painter = painterResource(id = R.drawable.revolt_onboarding), + contentDescription = stringResource(id = R.string.register_image_alt), + modifier = Modifier + .size(300.dp) + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = stringResource(R.string.register_instructions), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + style = MaterialTheme.typography.titleMedium.copy( + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + ), + modifier = Modifier + .padding(horizontal = 20.dp, vertical = 10.dp) + .fillMaxWidth() + ) + } + + Row { + TextButton(onClick = { + navController.popBackStack() + }) { + Text(text = stringResource(R.string.back)) + } + + Spacer(modifier = Modifier.width(10.dp)) + + Button(onClick = { + navController.navigate("register/details") + }) { + Text(text = stringResource(R.string.next)) + } + } + } +} + + + diff --git a/app/src/main/java/chat/revolt/screens/register/RegisterVerifyScreen.kt b/app/src/main/java/chat/revolt/screens/register/RegisterVerifyScreen.kt new file mode 100644 index 00000000..901e5db5 --- /dev/null +++ b/app/src/main/java/chat/revolt/screens/register/RegisterVerifyScreen.kt @@ -0,0 +1,103 @@ +package chat.revolt.screens.register + +import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import chat.revolt.R + +@Composable +fun RegisterVerifyScreen(navController: NavController, email: String) { + val intentLauncher = + rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {} + + Column( + modifier = Modifier + .fillMaxSize() + .padding(20.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Column( + modifier = Modifier + .weight(1f), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.check_mail), + style = MaterialTheme.typography.displaySmall.copy( + fontSize = 30.sp, + fontWeight = FontWeight.Black, + textAlign = TextAlign.Center + ), + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = stringResource(R.string.instructions_at_mail, email), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + style = MaterialTheme.typography.titleMedium.copy( + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + ), + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = stringResource(R.string.verify_then_choose_username), + color = MaterialTheme.colorScheme.onBackground.copy( + alpha = 0.5f + ), + style = MaterialTheme.typography.titleMedium.copy( + textAlign = TextAlign.Center, + fontWeight = FontWeight.Normal, + ), + modifier = Modifier + .padding(horizontal = 20.dp) + .fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(40.dp)) + + Button(onClick = { + val intent = Intent(Intent.ACTION_MAIN) + intent.addCategory(Intent.CATEGORY_APP_EMAIL) + intentLauncher.launch(intent) + }) { + Text(text = stringResource(R.string.open_mail_app)) + } + } + } +} + + + diff --git a/app/src/main/java/chat/revolt/ui/theme/Theme.kt b/app/src/main/java/chat/revolt/ui/theme/Theme.kt index 2992b858..eef00431 100644 --- a/app/src/main/java/chat/revolt/ui/theme/Theme.kt +++ b/app/src/main/java/chat/revolt/ui/theme/Theme.kt @@ -73,9 +73,11 @@ fun RevoltTheme( m3Supported && requestedTheme == Theme.M3Dynamic && systemInDarkTheme -> dynamicDarkColorScheme( context ) + m3Supported && requestedTheme == Theme.M3Dynamic && !systemInDarkTheme -> dynamicLightColorScheme( context ) + requestedTheme == Theme.Revolt -> RevoltColorScheme requestedTheme == Theme.Light -> LightColorScheme requestedTheme == Theme.Amoled -> AmoledColorScheme diff --git a/app/src/main/res/drawable/revolt_onboarding.xml b/app/src/main/res/drawable/revolt_onboarding.xml new file mode 100644 index 00000000..9372ddd4 --- /dev/null +++ b/app/src/main/res/drawable/revolt_onboarding.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d1810402..8cea7dcc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -32,17 +32,19 @@ Enter your email and we\'ll send you instructions on how to reset your password. Welcome to Revolt + Use Revolt to chat about stuff. It\'s like a place to hang out, get together, and talk about stuff. Best part, it\'s on the internet. Let\'s get you set up. - Your data is safe with us. We don\'t sell it, or use it to show you ads. + Your data is safe with us. We don\'t sell it, and neither do we use it to show you ads. Email We\'ll send you a verification email to confirm your account. Password - Eight or more, common ones are bad. Try messing around with a sentence. + Eight or more, common ones are bad. Symbols and numbers recommended. Check your mail! We\'ve sent further instructions to %1$s. Verify your email, and then we\'ll get on with choosing your username. + Open mail app Welcome! It\'s time to choose a username! @@ -90,12 +92,10 @@ Bot From linked channel - Welcome to Revolt\'s in-progress Android experience! - Select a server and channel by swiping from the left. - Unknown Home + Menu Log out Add server diff --git a/build.gradle b/build.gradle index d6eaeb26..5a6c5ee8 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ buildscript { // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.1.0-alpha11' apply false - id 'com.android.library' version '8.1.0-alpha11' apply false + id 'com.android.application' version '8.2.0-alpha01' apply false + id 'com.android.library' version '8.2.0-alpha01' apply false id 'org.jetbrains.kotlin.android' version '1.8.0' apply false id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0' apply false id "com.google.dagger.hilt.android" version "2.44" apply false