singleton (AuthRepository) repo refactor

This commit is contained in:
solovushka56
2025-12-11 19:29:55 +03:00
parent b74a74f3bd
commit 87bbfcc96c
12 changed files with 104 additions and 44 deletions

View File

@@ -20,6 +20,6 @@ class App : Application() {
super.onCreate() super.onCreate()
instance = this instance = this
AuthRepository.init(applicationContext) AuthRepository.getInstance(applicationContext)
} }
} }

View File

@@ -1,7 +1,10 @@
package ru.myitschool.work.core package ru.myitschool.work.core
object Constants { object Constants {
const val HOST = "http://10.0.2.2:8080" const val HOST = "http://10.0.2.2:8080"
// const val HOST = "http://127.0.0.1:8080"
const val AUTH_URL = "/auth" const val AUTH_URL = "/auth"
const val INFO_URL = "/info" const val INFO_URL = "/info"
const val BOOKING_URL = "/booking" const val BOOKING_URL = "/booking"

View File

@@ -7,42 +7,48 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import ru.myitschool.work.data.source.NetworkDataSource import ru.myitschool.work.data.source.NetworkDataSource
object AuthRepository { class AuthRepository private constructor(context: Context) {
private const val PREFS_NAME = "auth_prefs"
private const val KEY_CODE = "auth_code"
private const val KEY_NAME = "user_name"
private const val KEY_PHOTO = "user_photo"
private var _context: Context? = null companion object {
@Volatile
private var INSTANCE: AuthRepository? = null
fun getInstance(context: Context): AuthRepository {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: AuthRepository(context.applicationContext).also { INSTANCE = it }
}
}
fun clearInstance() {
INSTANCE = null
}
}
private val PREFS_NAME = "auth_prefs"
private val KEY_CODE = "auth_code"
private val KEY_NAME = "user_name"
private val KEY_PHOTO = "user_photo"
private val context: Context = context.applicationContext
private var codeCache: String? = null private var codeCache: String? = null
private var userCache: UserCache? = null private var userCache: UserCache? = null
private val _isAuthorized = MutableStateFlow(false) private val _isAuthorized = MutableStateFlow(false)
val isAuthorized: StateFlow<Boolean> = _isAuthorized.asStateFlow() val isAuthorized: StateFlow<Boolean> = _isAuthorized.asStateFlow()
fun init(context: Context) { init {
if (_context == null) { val prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
_context = context.applicationContext codeCache = prefs.getString(KEY_CODE, null)
val name = prefs.getString(KEY_NAME, null)
val photo = prefs.getString(KEY_PHOTO, null)
val prefs = _context!!.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) if (codeCache != null && name != null) {
codeCache = prefs.getString(KEY_CODE, null) userCache = UserCache(name, photo)
val name = prefs.getString(KEY_NAME, null) _isAuthorized.value = true
val photo = prefs.getString(KEY_PHOTO, null)
if (codeCache != null && name != null) {
userCache = UserCache(name, photo)
_isAuthorized.value = true
}
} }
} }
private fun requireContext(): Context { private fun getPrefs() = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
return _context ?: throw IllegalStateException(
"AuthRepository not inited"
)
}
private fun getPrefs() = requireContext().getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
suspend fun checkAndSave(text: String): Result<Boolean> { suspend fun checkAndSave(text: String): Result<Boolean> {
return NetworkDataSource.checkAuth(text).onSuccess { success -> return NetworkDataSource.checkAuth(text).onSuccess { success ->

View File

@@ -4,9 +4,10 @@ import ru.myitschool.work.data.model.BookingInfoResponse
import ru.myitschool.work.data.source.AuthException import ru.myitschool.work.data.source.AuthException
import ru.myitschool.work.data.source.NetworkDataSource import ru.myitschool.work.data.source.NetworkDataSource
object BookRepository { class BookRepository(private val authRepository: AuthRepository) {
suspend fun getAvailableBookings(): Result<BookingInfoResponse> { suspend fun getAvailableBookings(): Result<BookingInfoResponse> {
val code = AuthRepository.getCurrentCode() val code = authRepository.getCurrentCode()
return if (code != null) { return if (code != null) {
NetworkDataSource.getBookingInfo(code) NetworkDataSource.getBookingInfo(code)
} else { } else {
@@ -15,7 +16,7 @@ object BookRepository {
} }
suspend fun book(date: String, placeId: Int): Result<Unit> { suspend fun book(date: String, placeId: Int): Result<Unit> {
val code = AuthRepository.getCurrentCode() val code = authRepository.getCurrentCode()
return if (code != null) { return if (code != null) {
NetworkDataSource.book(code, date, placeId) NetworkDataSource.book(code, date, placeId)
} else { } else {

View File

@@ -4,9 +4,10 @@ import ru.myitschool.work.data.model.UserInfoResponse
import ru.myitschool.work.data.source.AuthException import ru.myitschool.work.data.source.AuthException
import ru.myitschool.work.data.source.NetworkDataSource import ru.myitschool.work.data.source.NetworkDataSource
object MainRepository { class MainRepository(private val authRepository: AuthRepository) {
suspend fun getUserInfo(): Result<UserInfoResponse> { suspend fun getUserInfo(): Result<UserInfoResponse> {
val code = AuthRepository.getCurrentCode() val code = authRepository.getCurrentCode()
return if (code != null) { return if (code != null) {
NetworkDataSource.getInfo(code) NetworkDataSource.getInfo(code)
} else { } else {

View File

@@ -6,7 +6,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
@@ -24,8 +26,10 @@ fun AppNavHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController() navController: NavHostController = rememberNavController()
) { ) {
// authorized state follow val context = LocalContext.current
val isAuthorized by AuthRepository.isAuthorized.collectAsState() val authRepository = remember { AuthRepository.getInstance(context) }
val isAuthorized by authRepository.isAuthorized.collectAsState()
LaunchedEffect(isAuthorized) { LaunchedEffect(isAuthorized) {
if (isAuthorized) { if (isAuthorized) {

View File

@@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -34,7 +35,7 @@ import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable @Composable
fun AuthScreen( fun AuthScreen(
viewModel: AuthViewModel = viewModel(), viewModel: AuthViewModel = viewModel(factory = AuthViewModelFactory(LocalContext.current)),
navController: NavController navController: NavController
) { ) {
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()

View File

@@ -1,6 +1,8 @@
package ru.myitschool.work.ui.screen.auth package ru.myitschool.work.ui.screen.auth
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -12,10 +14,11 @@ import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase
private val authRepo = AuthRepository
private val checkAndSaveAuthCodeUseCase = CheckAndSaveAuthCodeUseCase(authRepo)
class AuthViewModel : ViewModel() { class AuthViewModel(
private val authRepository: AuthRepository,
private val checkAndSaveAuthCodeUseCase: CheckAndSaveAuthCodeUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow<AuthState>(AuthState.Data()) private val _uiState = MutableStateFlow<AuthState>(AuthState.Data())
val uiState: StateFlow<AuthState> = _uiState.asStateFlow() val uiState: StateFlow<AuthState> = _uiState.asStateFlow()
@@ -68,6 +71,18 @@ class AuthViewModel : ViewModel() {
} }
} }
class AuthViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AuthViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val useCase = CheckAndSaveAuthCodeUseCase(authRepository)
return AuthViewModel(authRepository, useCase) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
sealed interface AuthAction { sealed interface AuthAction {
object NavigateToMain : AuthAction object NavigateToMain : AuthAction
} }

View File

@@ -19,13 +19,14 @@ import androidx.navigation.NavController
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookScreen( fun BookScreen(
viewModel: BookViewModel = viewModel(), viewModel: BookViewModel = viewModel(factory = BookViewModelFactory(LocalContext.current)),
navController: NavController navController: NavController
) { ) {
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()

View File

@@ -1,6 +1,8 @@
package ru.myitschool.work.ui.screen.book package ru.myitschool.work.ui.screen.book
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -9,14 +11,14 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.BookRepository import ru.myitschool.work.data.repo.BookRepository
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
private val bookRepo = BookRepository
class BookViewModel : ViewModel() { class BookViewModel(private val bookRepo: BookRepository) : ViewModel() {
private val _uiState = MutableStateFlow<BookState>(BookState.Loading) private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow() val uiState: StateFlow<BookState> = _uiState.asStateFlow()
@@ -180,3 +182,15 @@ sealed interface BookAction {
object NavigateBack : BookAction object NavigateBack : BookAction
object NavigateBackWithRefresh : BookAction object NavigateBackWithRefresh : BookAction
} }
class BookViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(BookViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val bookRepository = BookRepository(authRepository)
return BookViewModel(bookRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@@ -46,7 +46,7 @@ import ru.myitschool.work.ui.nav.BookScreenDestination
@Composable @Composable
fun MainScreen( fun MainScreen(
viewModel: MainViewModel = viewModel(), viewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current)),
navController: NavController navController: NavController
) { ) {
val state by viewModel.uiState.collectAsState() val state by viewModel.uiState.collectAsState()

View File

@@ -1,6 +1,8 @@
package ru.myitschool.work.ui.screen.main package ru.myitschool.work.ui.screen.main
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -14,10 +16,11 @@ import java.time.format.DateTimeFormatter
import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.MainRepository import ru.myitschool.work.data.repo.MainRepository
private val authRepo = AuthRepository
private val mainRepo = MainRepository
class MainViewModel : ViewModel() {
class MainViewModel(private val authRepo: AuthRepository,
private val mainRepo: MainRepository)
: ViewModel() {
private val _uiState = MutableStateFlow<MainState>(MainState.Loading) private val _uiState = MutableStateFlow<MainState>(MainState.Loading)
val uiState: StateFlow<MainState> = _uiState.asStateFlow() val uiState: StateFlow<MainState> = _uiState.asStateFlow()
@@ -95,6 +98,17 @@ class MainViewModel : ViewModel() {
} }
} }
class MainViewModelFactory(private val context: Context) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
val authRepository = AuthRepository.getInstance(context)
val mainRepository = MainRepository(authRepository)
return MainViewModel(authRepository, mainRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
sealed interface MainAction { sealed interface MainAction {
object NavigateToAuth : MainAction object NavigateToAuth : MainAction
object NavigateToBooking : MainAction object NavigateToBooking : MainAction