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">
|
android:theme="@style/Theme.DebtPirate">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</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>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.host
|
package ing.bikeshedengineer.debtpirate.app.host
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.SystemBarStyle
|
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
|
@ -18,7 +20,9 @@ import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.navigation
|
import androidx.navigation.compose.navigation
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import androidx.navigation.toRoute
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
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.login.LoginScreen
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||||
|
@ -37,6 +41,9 @@ class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
handleIntent(intent)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
@ -79,15 +86,50 @@ class MainActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
navigation<Destination.AuthGraph>(
|
navigation<Destination.AuthGraph>(
|
||||||
startDestination = Destination.LoginScreen
|
startDestination = Destination.RegistrationConfirmationCheckEmailScreen(
|
||||||
|
emailAddress = "zachary@dziura.email"
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
composable<Destination.LoginScreen>() { LoginScreen() }
|
composable<Destination.LoginScreen>() { LoginScreen() }
|
||||||
composable<Destination.RegistrationScreen>() { RegistrationScreen() }
|
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
|
@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)
|
val request = UserCreatePostRequest(emailAddress, name, password)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val response = userRepository.submitCreateUserRequest(request)
|
val response = userRepository.createUserPostRequest(request)
|
||||||
Log.d("RegistrationScreen", "Account registration successful! $response")
|
Log.d("RegistrationScreen", "Account registration successful! $response")
|
||||||
return response
|
return response
|
||||||
} catch (err: Throwable) {
|
} 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.ApiResponse
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
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.UserCreatePostResponse
|
||||||
|
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserVerificationGetResponse
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.Field
|
||||||
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
interface UserEndpoint {
|
interface UserEndpoint {
|
||||||
@POST("user")
|
@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.UserCreatePostRequest
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostResponse
|
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 {
|
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.endpoint.UserEndpoint
|
||||||
import ing.bikeshedengineer.debtpirate.data.remote.model.user.UserCreatePostRequest
|
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.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 ing.bikeshedengineer.debtpirate.data.repository.UserRepository
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -15,13 +17,31 @@ import retrofit2.Retrofit
|
||||||
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
class UserRepositoryImpl(httpClient: Retrofit) : UserRepository {
|
||||||
private val userEndpoint: UserEndpoint = httpClient.create(UserEndpoint::class.java)
|
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) {
|
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) {
|
if (response.isSuccessful) {
|
||||||
val body = response.body()
|
val body = response.body()
|
||||||
Log.d("Registration", "$body")
|
|
||||||
return@withContext body!!.data!!
|
return@withContext body!!.data!!
|
||||||
} else {
|
} else {
|
||||||
val gson = Gson()
|
val gson = Gson()
|
||||||
|
|
|
@ -13,4 +13,10 @@ sealed interface Destination {
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object RegistrationScreen : Destination
|
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"
|
activityCompose = "1.9.3"
|
||||||
agp = "8.7.2"
|
agp = "8.7.2"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
composeBom = "2024.10.01"
|
composeBom = "2024.11.00"
|
||||||
coreKtx = "1.15.0"
|
coreKtx = "1.15.0"
|
||||||
datastore = "1.1.1"
|
datastore = "1.1.1"
|
||||||
espressoCore = "3.6.1"
|
espressoCore = "3.6.1"
|
||||||
|
@ -17,8 +17,8 @@ lifecycleRuntimeKtx = "2.8.7"
|
||||||
lifecycleViewModelKtx = "2.8.7"
|
lifecycleViewModelKtx = "2.8.7"
|
||||||
lifecycleViewmodelCompose = "2.8.7"
|
lifecycleViewmodelCompose = "2.8.7"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
material3 = "1.4.0-alpha03"
|
material3 = "1.4.0-alpha04"
|
||||||
navigation = "2.8.3"
|
navigation = "2.8.4"
|
||||||
protobuf = "0.9.4"
|
protobuf = "0.9.4"
|
||||||
protoLite = "3.21.11"
|
protoLite = "3.21.11"
|
||||||
okhttp = "4.10.0"
|
okhttp = "4.10.0"
|
||||||
|
|
Loading…
Add table
Reference in a new issue