feat: registration flow

Signed-off-by: Infi <wingit@geist.ga>
This commit is contained in:
Infi 2023-05-11 00:20:40 +02:00
parent 11892e54a2
commit e316c0bf3a
19 changed files with 650 additions and 89 deletions

View File

@ -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'

View File

@ -31,7 +31,7 @@
android:value="false" />
<activity
android:name=".MainActivity"
android:name=".activities.MainActivity"
android:exported="true"
android:theme="@style/Theme.Revolt">
<intent-filter>

View File

@ -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) }

View File

@ -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<Unit, RevoltError> {
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)
}

View File

@ -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)

View File

@ -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
)
)
}

View File

@ -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

View File

@ -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()

View File

@ -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 ?: "",

View File

@ -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"
)
}
}
}
}

View File

@ -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))
}
}
}
}

View File

@ -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))
}
}
}

View File

@ -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<String?>(null)
private var captchaToken by mutableStateOf<String?>(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))
}
}
}
}

View File

@ -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))
}
}
}
}

View File

@ -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))
}
}
}
}

View File

@ -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

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="113.68dp"
android:height="186.04dp"
android:viewportWidth="113.68"
android:viewportHeight="186.04">
<path
android:pathData="m16.4,82.04c-4.55,-0.64 -8.73,2.52 -9.37,7.07l-6.94,49.6c-0.64,4.55 2.52,8.74 7.07,9.37l74.16,10.38 20.8,27.57 4.54,-32.41 5.02,-35.84 1.93,-13.76c0.64,-4.55 -2.52,-8.73 -7.07,-9.37z"
android:strokeWidth="0.445733"
android:fillColor="#1c243c" />
<path
android:pathData="m65.51,116.45c-0.41,2.91 -2.24,4.44 -5.64,3.97l-5.62,-0.79 1.29,-9.22 5.62,0.79c3.39,0.47 4.73,2.5 4.34,5.26zM42.11,100.86 L46.49,109.16 42.34,138.82 51.4,140.09 53.36,126.05 55.54,126.35 61.31,141.48 71.54,142.91 65.02,126.98c5.11,-0.51 9.17,-4.5 9.77,-9.6 0.91,-6.47 -2.92,-12.35 -12.03,-13.63z"
android:strokeWidth="1.05602"
android:fillColor="#ff4654"
android:strokeColor="#ff4654" />
<path
android:pathData="m105.84,54.37 l-29.08,8.08a3.35,3.35 0,0 0,-2.33 4.13l3.59,12.92a3.35,3.35 0,0 0,4.13 2.33l29.08,-8.08a3.35,3.35 0,0 0,2.33 -4.13l-3.59,-12.92a3.35,3.35 0,0 0,-4.13 -2.33m-13.01,15.8 l-4.85,1.35 1.35,4.85 -3.23,0.9 -1.35,-4.85 -4.85,1.35 -0.9,-3.23 4.85,-1.35 -1.35,-4.85 3.23,-0.9 1.35,4.85 4.85,-1.35m9.06,4.44a2.51,2.51 0,0 1,-3.1 -1.75,2.51 2.51,0 0,1 1.75,-3.1 2.51,2.51 0,0 1,3.1 1.75,2.51 2.51,0 0,1 -1.75,3.1m5.12,-6.64a2.51,2.51 0,0 1,-3.1 -1.75,2.51 2.51,0 0,1 1.75,-3.1 2.51,2.51 0,0 1,3.1 1.75,2.51 2.51,0 0,1 -1.75,3.1z"
android:strokeWidth="1.67655"
android:fillColor="#6fe465" />
<path
android:pathData="m25.33,54.07c-0.73,1.26 -2.32,1.69 -3.57,0.96L3.62,44.58c-1.25,-0.72 -1.69,-2.32 -0.96,-3.57 0.73,-1.26 2.33,-1.68 3.57,-0.96l10.2,5.88 1.53,3.9 4.14,-0.63 2.27,1.31c1.26,0.73 1.68,2.33 0.96,3.57M20.53,36.2c-10.2,-5.88 -14.12,0.92 -14.12,0.92l20.41,11.76c0,0 3.92,-6.8 -6.28,-12.68M0.53,47.33c-1.08,1.88 -0.44,4.28 1.44,5.36l13.6,7.84c1.88,1.08 4.28,0.44 5.36,-1.44L21.59,57.95 1.18,46.19Z"
android:strokeWidth="1.30846"
android:fillColor="#00abff" />
<path
android:pathData="m80.17,19.65c3.38,0.12 7.09,0.49 10.95,1.22l-1.18,6.16 -26.2,-5c0.19,-1 5.56,-2.36 13.24,-2.43l1.91,-10.02c-1.37,0.14 -2.63,0.79 -3.54,1.8 -0.64,-1.75 -2.17,-3.13 -4.12,-3.5 -1.96,-0.37 -3.88,0.35 -5.12,1.74 1.1,-5.51 7.31,-9.02 14.36,-8.3l0.01,-0.06c0.16,-0.85 0.98,-1.41 1.83,-1.25 0.85,0.16 1.41,0.98 1.25,1.83l-0.01,0.06c6.82,1.92 11.29,7.47 10.3,13 -0.64,-1.75 -2.17,-3.13 -4.12,-3.5 -1.96,-0.37 -3.88,0.35 -5.12,1.74 -0.48,-1.27 -1.41,-2.34 -2.62,-2.99l-1.81,9.48"
android:strokeWidth="1.5689"
android:fillColor="#fbe94e" />
</vector>

View File

@ -32,17 +32,19 @@
<string name="password_forgot_instructions">Enter your email and we\'ll send you instructions on how to reset your password.</string>
<string name="register_heading">Welcome to Revolt</string>
<string name="register_image_alt">Use Revolt to chat about stuff.</string>
<string name="register_instructions">It\'s like a place to hang out, get together, and talk about stuff. Best part, it\'s on the internet.</string>
<string name="register_form_heading">Let\'s get you set up.</string>
<string name="register_data">Your data is safe with us. We don\'t sell it, or use it to show you ads.</string>
<string name="register_data">Your data is safe with us. We don\'t sell it, and neither do we use it to show you ads.</string>
<string name="register_email">Email</string>
<string name="register_email_verification_hint">We\'ll send you a verification email to confirm your account.</string>
<string name="register_password">Password</string>
<string name="register_password_rules">Eight or more, common ones are bad. Try messing around with a sentence.</string>
<string name="register_password_rules">Eight or more, common ones are bad. Symbols and numbers recommended.</string>
<string name="check_mail">Check your mail!</string>
<string name="instructions_at_mail">We\'ve sent further instructions to %1$s.</string>
<string name="verify_then_choose_username">Verify your email, and then we\'ll get on with choosing your username.</string>
<string name="open_mail_app">Open mail app</string>
<string name="welcome">Welcome!</string>
<string name="username_choose_lead">It\'s time to choose a username!</string>
@ -90,12 +92,10 @@
<string name="badge_bot_alt">Bot</string>
<string name="badge_masquerade_alt">From linked channel</string>
<string name="tutorial">Welcome to Revolt\'s in-progress Android experience!</string>
<string name="select_channel">Select a server and channel by swiping from the left.</string>
<string name="unknown">Unknown</string>
<string name="home">Home</string>
<string name="menu">Menu</string>
<string name="logout">Log out</string>
<string name="server_plus_alt">Add server</string>

View File

@ -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