feat(auth): 手机号验证码登录(会话模型 + 登录页 + 导航接入)
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
package com.mingyuan.android_client.core.data.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.mingyuan.android_client.core.data.repository.FakeCheckinRepository
|
||||
import com.mingyuan.android_client.core.data.repository.FakeHomeRepository
|
||||
import com.mingyuan.android_client.core.data.repository.FakePlanRepository
|
||||
@@ -12,7 +17,9 @@ import com.mingyuan.android_client.core.domain.repository.ProfileRepository
|
||||
import com.mingyuan.android_client.core.domain.repository.SessionRepository
|
||||
import dagger.Binds
|
||||
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
|
||||
|
||||
@@ -39,4 +46,11 @@ abstract class DataModule {
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindSessionRepository(impl: FakeSessionRepository): SessionRepository
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSessionDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||
PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("session") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,71 @@
|
||||
package com.mingyuan.android_client.core.data.repository
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.mingyuan.android_client.core.domain.repository.LoginResult
|
||||
import com.mingyuan.android_client.core.domain.repository.SessionRepository
|
||||
import com.mingyuan.android_client.core.domain.repository.SmsCodeResult
|
||||
import com.mingyuan.android_client.core.model.Session
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Local-mock session implementation. It simulates the "phone + SMS code" login flow and persists
|
||||
* the token via DataStore so the login survives process restarts (mirroring the mini-program's
|
||||
* `wx.getStorageSync('token')`). Swap this for a network-backed implementation when the backend
|
||||
* endpoints are ready.
|
||||
*
|
||||
* Mock credentials: phone `13812341234`, code `123456`.
|
||||
*/
|
||||
@Singleton
|
||||
class FakeSessionRepository @Inject constructor() : SessionRepository {
|
||||
class FakeSessionRepository @Inject constructor(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) : SessionRepository {
|
||||
|
||||
private val _isLoggedIn = MutableStateFlow(true)
|
||||
override val isLoggedIn: Flow<Boolean> = _isLoggedIn
|
||||
private companion object {
|
||||
val TOKEN_KEY = stringPreferencesKey("token")
|
||||
const val MOCK_SMS_CODE = "123456"
|
||||
val PHONE_PATTERN = Regex("^1\\d{10}$")
|
||||
// Mirrors the backend's "phone must be pre-provisioned" constraint.
|
||||
val PHONE_WHITELIST = setOf("13812341234")
|
||||
}
|
||||
|
||||
override suspend fun login() {
|
||||
delay(300)
|
||||
_isLoggedIn.value = true
|
||||
private var pendingCode: String? = null
|
||||
private var pendingPhone: String? = null
|
||||
|
||||
override val session: Flow<Session?> = dataStore.data
|
||||
.map { prefs -> prefs[TOKEN_KEY]?.let(::Session) }
|
||||
|
||||
override suspend fun requestSmsCode(phone: String): SmsCodeResult {
|
||||
if (!PHONE_PATTERN.matches(phone)) {
|
||||
return SmsCodeResult.Failure("请输入正确的手机号")
|
||||
}
|
||||
delay(500)
|
||||
pendingPhone = phone
|
||||
pendingCode = MOCK_SMS_CODE
|
||||
return SmsCodeResult.Success
|
||||
}
|
||||
|
||||
override suspend fun login(phone: String, code: String): LoginResult {
|
||||
if (phone != pendingPhone || code != pendingCode) {
|
||||
return LoginResult.Failure("验证码错误")
|
||||
}
|
||||
if (phone !in PHONE_WHITELIST) {
|
||||
return LoginResult.Failure("手机号未匹配,无法登录")
|
||||
}
|
||||
delay(500)
|
||||
dataStore.edit { it[TOKEN_KEY] = "mock-token-$phone" }
|
||||
pendingPhone = null
|
||||
pendingCode = null
|
||||
return LoginResult.Success
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
delay(300)
|
||||
_isLoggedIn.value = false
|
||||
dataStore.edit { it.remove(TOKEN_KEY) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user