Create confirmation screen
This commit is contained in:
parent
f3eb73ee42
commit
f107994f0b
11 changed files with 213 additions and 12 deletions
|
@ -20,9 +20,19 @@
|
|||
android:theme="@style/Theme.DebtPirate">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="debtpirate.app" />
|
||||
<data android:pathPattern="/verify" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.host
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.SystemBarStyle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||
|
@ -18,7 +20,9 @@ import androidx.navigation.compose.NavHost
|
|||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.toRoute
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationScreen
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginScreen
|
||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||
|
@ -37,6 +41,9 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
handleIntent(intent)
|
||||
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
val navController = rememberNavController()
|
||||
|
@ -79,15 +86,50 @@ class MainActivity : ComponentActivity() {
|
|||
}
|
||||
) {
|
||||
navigation<Destination.AuthGraph>(
|
||||
startDestination = Destination.LoginScreen
|
||||
startDestination = Destination.RegistrationConfirmationCheckEmailScreen(
|
||||
emailAddress = "zachary@dziura.email"
|
||||
)
|
||||
) {
|
||||
composable<Destination.LoginScreen>() { LoginScreen() }
|
||||
composable<Destination.RegistrationScreen>() { RegistrationScreen() }
|
||||
composable<Destination.RegistrationConfirmationCheckEmailScreen>() {
|
||||
val (emailAddress, _) = it.toRoute<Destination.RegistrationConfirmationCheckEmailScreen>()
|
||||
ConfirmationScreen(
|
||||
emailAddress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleIntent(intent)
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent) {
|
||||
val action: String? = intent.action
|
||||
val actionUri: Uri? = intent.data
|
||||
|
||||
if (Intent.ACTION_VIEW == action) {
|
||||
when (actionUri?.lastPathSegment) {
|
||||
"verify" -> {
|
||||
val userId = actionUri.getQueryParameter("u")!!.toInt()
|
||||
val verificationToken = actionUri.getQueryParameter("t")!!
|
||||
|
||||
handleNewUserVerificationIntent(userId, verificationToken)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewUserVerificationIntent(userId: Int, sessionToken: String) {
|
||||
Log.d("DebtPirate::MainActivity", "User ID: $userId, Session Token: $sessionToken")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.MarkEmailRead
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable()
|
||||
fun ConfirmationScreen(emailAddress: String? = null, redirectedFromEmailConfirmation: Boolean = false) {
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { padding ->
|
||||
Column(modifier = Modifier.padding(padding).padding(PaddingValues(4.dp))) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Outlined.MarkEmailRead,
|
||||
null,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.size(96.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(3f)
|
||||
) {
|
||||
Text(
|
||||
"Confirmation Sent!",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
if (emailAddress != null) {
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("We sent your confirmation email to ")
|
||||
withStyle(
|
||||
style = SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
) {
|
||||
append(emailAddress)
|
||||
}
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 16.dp))
|
||||
|
||||
)
|
||||
|
||||
Text(
|
||||
"Follow the instructions listed in that email to finish registering your new account.",
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(PaddingValues(top = 8.dp))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ class SubmitAccountRegistrationRequestUseCase(private val userRepository: UserRe
|
|||
val request = UserCreatePostRequest(emailAddress, name, password)
|
||||
|
||||
try {
|
||||
val response = userRepository.submitCreateUserRequest(request)
|
||||
val response = userRepository.createUserPostRequest(request)
|
||||
Log.d("RegistrationScreen", "Account registration successful! $response")
|
||||
return response
|
||||
} catch (err: Throwable) {
|
||||
|
|
|
@ -3,11 +3,21 @@ package ing.bikeshedengineer.debtpirate.data.remote.endpoint
|
|||
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface UserEndpoint {
|
||||
@POST("user")
|
||||
suspend fun submitUserPostRequest(@Body request: UserCreatePostRequest): Response<ApiResponse<UserCreatePostResponse>>
|
||||
suspend fun createUserPostRequest(@Body request: UserCreatePostRequest): Response<ApiResponse<UserCreatePostResponse>>
|
||||
|
||||
@GET("user/{userId}/verify")
|
||||
suspend fun userVerificationGetRequest(
|
||||
@Path("userId") userId: Int,
|
||||
@Field("t") verificationToken: String
|
||||
): Response<ApiResponse<UserVerificationGetResponse>>
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.model.user
|
||||
|
||||
data class UserVerificationGetRequest(val userId: Int, val verificationToken: String)
|
|
@ -0,0 +1,18 @@
|
|||
package ing.bikeshedengineer.debtpirate.data.remote.model.user
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter
|
||||
import ing.bikeshedengineer.debtpirate.domain.adapter.OffsetDateTimeAdapter
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
data class UserVerificationGetResponse(
|
||||
val userId: Int,
|
||||
val session: UserVerificationGetResponseTokenData,
|
||||
val auth: UserVerificationGetResponseTokenData
|
||||
)
|
||||
|
||||
data class UserVerificationGetResponseTokenData(
|
||||
val token: String,
|
||||
@JsonAdapter(OffsetDateTimeAdapter::class) val expiresAt: OffsetDateTime
|
||||
)
|
||||
|
||||
|
|
@ -2,7 +2,10 @@ package ing.bikeshedengineer.debtpirate.data.repository
|
|||
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||
|
||||
interface UserRepository {
|
||||
suspend fun submitCreateUserRequest(request: UserCreatePostRequest): UserCreatePostResponse
|
||||
suspend fun createUserPostRequest(request: UserCreatePostRequest): UserCreatePostResponse
|
||||
suspend fun verifyUserGetRequest(request: UserVerificationGetRequest): UserVerificationGetResponse
|
||||
}
|
|
@ -7,6 +7,8 @@ import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
|
|||
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.UserEndpoint
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetRequest
|
||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||
import ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -15,13 +17,31 @@ import retrofit2.Retrofit
|
|||
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
||||
private val userEndpoint: UserEndpoint = httpClient.create(UserEndpoint::class.java)
|
||||
|
||||
override suspend fun submitCreateUserRequest(request: UserCreatePostRequest): UserCreatePostResponse {
|
||||
override suspend fun createUserPostRequest(request: UserCreatePostRequest): UserCreatePostResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val response = userEndpoint.submitUserPostRequest(request)
|
||||
val response = userEndpoint.createUserPostRequest(request)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
val errorType = object : TypeToken<ApiResponse<Unit>>() {}.type
|
||||
val body =
|
||||
gson.fromJson<ApiResponse<Unit>>(response.errorBody()!!.charStream(), errorType)
|
||||
|
||||
throw Throwable(body!!.error!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun verifyUserGetRequest(request: UserVerificationGetRequest): UserVerificationGetResponse {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val (userId, verificationToken) = request
|
||||
val response = userEndpoint.userVerificationGetRequest(userId, verificationToken)
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val body = response.body()
|
||||
Log.d("Registration", "$body")
|
||||
return@withContext body!!.data!!
|
||||
} else {
|
||||
val gson = Gson()
|
||||
|
|
|
@ -13,4 +13,10 @@ sealed interface Destination {
|
|||
|
||||
@Serializable
|
||||
data object RegistrationScreen : Destination
|
||||
|
||||
@Serializable
|
||||
data class RegistrationConfirmationCheckEmailScreen(
|
||||
val emailAddress: String? = null,
|
||||
val comingFromEmail: Boolean = false
|
||||
) : Destination
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
activityCompose = "1.9.3"
|
||||
agp = "8.7.2"
|
||||
appcompat = "1.7.0"
|
||||
composeBom = "2024.10.01"
|
||||
composeBom = "2024.11.00"
|
||||
coreKtx = "1.15.0"
|
||||
datastore = "1.1.1"
|
||||
espressoCore = "3.6.1"
|
||||
|
@ -17,8 +17,8 @@ lifecycleRuntimeKtx = "2.8.7"
|
|||
lifecycleViewModelKtx = "2.8.7"
|
||||
lifecycleViewmodelCompose = "2.8.7"
|
||||
material = "1.12.0"
|
||||
material3 = "1.4.0-alpha03"
|
||||
navigation = "2.8.3"
|
||||
material3 = "1.4.0-alpha04"
|
||||
navigation = "2.8.4"
|
||||
protobuf = "0.9.4"
|
||||
protoLite = "3.21.11"
|
||||
okhttp = "4.10.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue