Skip to content

Commit

Permalink
Add registration flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Maruchin1 committed Apr 17, 2023
1 parent 850055d commit d23f6e3
Show file tree
Hide file tree
Showing 59 changed files with 1,236 additions and 88 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {
implementation(libs.accompanist.placeholder)
implementation(libs.retrofit)
implementation(libs.retrofit.gson)
implementation(libs.datastore.preferences)
kapt(libs.hilt.android.compiler)
testImplementation(libs.junit)
testImplementation(libs.coroutines.test)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
android:theme="@style/Theme.DomainDrivenAndroid"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name=".ui.main.MainActivity"
android:exported="true"
android:theme="@style/Theme.DomainDrivenAndroid">
<intent-filter>
Expand Down
20 changes: 0 additions & 20 deletions app/src/main/java/com/maruchin/domaindrivenandroid/MainActivity.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.maruchin.domaindrivenandroid.core

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
class DataStoreModule {
private val Context.dataStore by preferencesDataStore(name = "preferences")

@Provides
@Singleton
fun dataStore(@ApplicationContext context: Context): DataStore<Preferences> {
return context.dataStore
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.maruchin.domaindrivenandroid.data

import com.maruchin.domaindrivenandroid.data.account.AccountApi
import com.maruchin.domaindrivenandroid.data.account.AccountStorage
import com.maruchin.domaindrivenandroid.data.account.DefaultAccountStorage
import com.maruchin.domaindrivenandroid.data.account.FakeAccountApi
import com.maruchin.domaindrivenandroid.data.activationCode.ActivationCodesApi
import com.maruchin.domaindrivenandroid.data.activationCode.FakeActivationCodesApi
import com.maruchin.domaindrivenandroid.data.coupon.CouponsApi
Expand All @@ -19,6 +23,12 @@ abstract class DataModule {
@Binds
abstract fun activationCodesApi(impl: FakeActivationCodesApi): ActivationCodesApi

@Binds
abstract fun accountApi(impl: FakeAccountApi): AccountApi

@Binds
abstract fun accountStorage(impl: DefaultAccountStorage): AccountStorage


companion object {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.maruchin.domaindrivenandroid.data.account

interface AccountApi {

suspend fun createAccount(username: String, email: String): AccountJson
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.maruchin.domaindrivenandroid.data.account

data class AccountJson(val username: String, val email: String, val collectedPoints: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.maruchin.domaindrivenandroid.data.account

import com.maruchin.domaindrivenandroid.data.units.Points

fun AccountJson.toModel() = Account(
username = username,
email = email,
collectedPoints = Points(collectedPoints),
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
package com.maruchin.domaindrivenandroid.data.account

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AccountRepository @Inject constructor() {
private val account = MutableStateFlow<Account?>(sampleAccount)
class AccountRepository @Inject constructor(
private val accountApi: AccountApi,
private val accountStorage: AccountStorage,
) {

fun getLoggedInAccount(): Flow<Account?> {
return account
return accountStorage.getLoggedInAccount()
}

suspend fun saveLoggedInAccount(account: Account?) {
this.account.emit(account)
suspend fun saveLoggedInAccount(account: Account) {
accountStorage.saveLoggedInAccount(account)
}

suspend fun clearLoggedInAccount() {
accountStorage.saveLoggedInAccount(null)
}

suspend fun createAccount(username: String, email: String) {
val accountFromApi = accountApi.createAccount(username, email)
accountStorage.saveLoggedInAccount(accountFromApi.toModel())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.maruchin.domaindrivenandroid.data.account

import kotlinx.coroutines.flow.Flow

interface AccountStorage {

fun getLoggedInAccount(): Flow<Account?>

suspend fun saveLoggedInAccount(account: Account?)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.maruchin.domaindrivenandroid.data.account

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import com.maruchin.domaindrivenandroid.data.units.Points
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

val ACCOUNT_USERNAME = stringPreferencesKey("account_username")
val ACCOUNT_EMAIL = stringPreferencesKey("account_email")
val ACCOUNT_COLLECTED_POINTS = intPreferencesKey("account_collected_points")

class DefaultAccountStorage @Inject constructor(
private val dataStore: DataStore<Preferences>
) : AccountStorage {

override fun getLoggedInAccount(): Flow<Account?> {
return dataStore.data.map { preferences ->
val username = preferences[ACCOUNT_USERNAME]
val email = preferences[ACCOUNT_EMAIL]
val collectedPoints = preferences[ACCOUNT_COLLECTED_POINTS]
if (username != null && email != null && collectedPoints != null) {
Account(
username = username,
email = email,
collectedPoints = Points(collectedPoints)
)
} else null
}
}

override suspend fun saveLoggedInAccount(account: Account?) {
dataStore.edit { preferences ->
if (account == null) {
preferences.remove(ACCOUNT_USERNAME)
preferences.remove(ACCOUNT_EMAIL)
preferences.remove(ACCOUNT_COLLECTED_POINTS)
} else {
preferences[ACCOUNT_USERNAME] = account.username
preferences[ACCOUNT_EMAIL] = account.email
preferences[ACCOUNT_COLLECTED_POINTS] = account.collectedPoints.value
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.maruchin.domaindrivenandroid.data.account

import javax.inject.Inject

class FakeAccountApi @Inject constructor() : AccountApi {

override suspend fun createAccount(username: String, email: String): AccountJson {
return AccountJson(username = username, email = email, collectedPoints = 170)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.maruchin.domaindrivenandroid.data.account

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow

class FakeAccountStorage : AccountStorage {
private val account = MutableStateFlow<Account?>(sampleAccount)

override fun getLoggedInAccount(): Flow<Account?> {
return account
}

override suspend fun saveLoggedInAccount(account: Account?) {
this.account.emit(account)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.maruchin.domaindrivenandroid.data.registrationrequest

data class PersonalData(val username: String, val email: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.maruchin.domaindrivenandroid.data.registrationrequest

data class RegistrationRequest(
val personalData: PersonalData? = null,
val termsAndConditionsAccepted: Boolean = false,
) {

fun setPersonalData(personalData: PersonalData) = copy(
personalData = personalData,
)

fun acceptTermsAndConditions() = copy(
termsAndConditionsAccepted = true,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.maruchin.domaindrivenandroid.data.registrationrequest

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class RegistrationRequestRepository @Inject constructor() {
private val registrationRequest = MutableStateFlow<RegistrationRequest?>(null)

fun getRegistrationRequest(): Flow<RegistrationRequest?> {
return registrationRequest
}

suspend fun saveRegistrationRequest(request: RegistrationRequest) {
registrationRequest.emit(request)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.maruchin.domaindrivenandroid.data.coupon.sampleCoupons
data class CollectableCoupon(val coupon: Coupon, val canCollect: Boolean)

val sampleCollectableCoupons = listOf(
CollectableCoupon(coupon = sampleCoupons[0], canCollect = false),
CollectableCoupon(coupon = sampleCoupons[1], canCollect = true),
CollectableCoupon(coupon = sampleCoupons[2], canCollect = true),
CollectableCoupon(coupon = sampleCoupons[1], canCollect = true),
CollectableCoupon(coupon = sampleCoupons[0], canCollect = false),
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class GetAllCollectableCouponsUseCase @Inject constructor(
accountRepository.getLoggedInAccount().filterNotNull(),
couponsRepository.getAllCoupons()
) { account, allCoupons ->
allCoupons.map { coupon ->
allCoupons.sortedBy {
it.points
}.map { coupon ->
CollectableCoupon(coupon = coupon, canCollect = account.canPayFor(coupon))
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.maruchin.domaindrivenandroid.domain.registrationrequest

import com.maruchin.domaindrivenandroid.data.registrationrequest.RegistrationRequestRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class AcceptRegistrationTermsAndConditionsUseCase @Inject constructor(
private val registrationRequestRepository: RegistrationRequestRepository,
) {

suspend operator fun invoke() {
var request = checkNotNull(registrationRequestRepository.getRegistrationRequest().first())
request = request.acceptTermsAndConditions()
registrationRequestRepository.saveRegistrationRequest(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.maruchin.domaindrivenandroid.domain.registrationrequest

import com.maruchin.domaindrivenandroid.data.account.AccountRepository
import com.maruchin.domaindrivenandroid.data.registrationrequest.RegistrationRequestRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class CompleteRegistrationUseCase @Inject constructor(
private val registrationRequestRepository: RegistrationRequestRepository,
private val accountRepository: AccountRepository,
) {

suspend operator fun invoke() {
val request = checkNotNull(registrationRequestRepository.getRegistrationRequest().first())
check(request.termsAndConditionsAccepted)
val personalData = checkNotNull(request.personalData)
accountRepository.createAccount(
username = personalData.username,
email = personalData.email,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.maruchin.domaindrivenandroid.domain.registrationrequest

import com.maruchin.domaindrivenandroid.data.registrationrequest.PersonalData
import com.maruchin.domaindrivenandroid.data.registrationrequest.RegistrationRequestRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class SetRegistrationPersonalDataUseCase @Inject constructor(
private val registrationRequestRepository: RegistrationRequestRepository,
) {

suspend operator fun invoke(personalData: PersonalData) {
var request = checkNotNull(registrationRequestRepository.getRegistrationRequest().first())
request = request.setPersonalData(personalData)
registrationRequestRepository.saveRegistrationRequest(request)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.maruchin.domaindrivenandroid.domain.registrationrequest

import com.maruchin.domaindrivenandroid.data.registrationrequest.RegistrationRequest
import com.maruchin.domaindrivenandroid.data.registrationrequest.RegistrationRequestRepository
import javax.inject.Inject

class StartNewRegistrationUseCase @Inject constructor(
private val registrationRequestRepository: RegistrationRequestRepository,
) {

suspend operator fun invoke() {
val newRequest = RegistrationRequest()
registrationRequestRepository.saveRegistrationRequest(newRequest)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.maruchin.domaindrivenandroid.ui

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun FieldErrorView(error: String?) {
if (error != null) {
Text(
text = error,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.fillMaxWidth(),
)
}
}
Loading

0 comments on commit d23f6e3

Please sign in to comment.