From 87bbfcc96ceef104192376c5901e30c48afadf39 Mon Sep 17 00:00:00 2001 From: solovushka56 <75mehanik@gmail.com> Date: Thu, 11 Dec 2025 19:29:55 +0300 Subject: [PATCH] singleton (AuthRepository) repo refactor --- app/src/main/java/ru/myitschool/work/App.kt | 2 +- .../java/ru/myitschool/work/core/Constants.kt | 3 + .../work/data/repo/AuthRepository.kt | 56 ++++++++++--------- .../work/data/repo/BookRepository.kt | 7 ++- .../work/data/repo/MainRepository.kt | 5 +- .../work/ui/screen/NavigationGraph.kt | 8 ++- .../work/ui/screen/auth/AuthScreen.kt | 3 +- .../work/ui/screen/auth/AuthViewModel.kt | 21 ++++++- .../work/ui/screen/book/BookScreen.kt | 3 +- .../work/ui/screen/book/BookViewModel.kt | 18 +++++- .../work/ui/screen/main/MainScreen.kt | 2 +- .../work/ui/screen/main/MainViewModel.kt | 20 ++++++- 12 files changed, 104 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/ru/myitschool/work/App.kt b/app/src/main/java/ru/myitschool/work/App.kt index a878087..72e003b 100644 --- a/app/src/main/java/ru/myitschool/work/App.kt +++ b/app/src/main/java/ru/myitschool/work/App.kt @@ -20,6 +20,6 @@ class App : Application() { super.onCreate() instance = this - AuthRepository.init(applicationContext) + AuthRepository.getInstance(applicationContext) } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/core/Constants.kt b/app/src/main/java/ru/myitschool/work/core/Constants.kt index a8b7cc5..70070ea 100644 --- a/app/src/main/java/ru/myitschool/work/core/Constants.kt +++ b/app/src/main/java/ru/myitschool/work/core/Constants.kt @@ -1,7 +1,10 @@ package ru.myitschool.work.core object Constants { + 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 INFO_URL = "/info" const val BOOKING_URL = "/booking" diff --git a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt index 2a54875..760e2b9 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt @@ -7,42 +7,48 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import ru.myitschool.work.data.source.NetworkDataSource -object AuthRepository { - 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" +class AuthRepository private constructor(context: Context) { - 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 userCache: UserCache? = null private val _isAuthorized = MutableStateFlow(false) val isAuthorized: StateFlow = _isAuthorized.asStateFlow() - fun init(context: Context) { - if (_context == null) { - _context = context.applicationContext + init { + val prefs = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + 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) - codeCache = prefs.getString(KEY_CODE, null) - val name = prefs.getString(KEY_NAME, null) - val photo = prefs.getString(KEY_PHOTO, null) - - if (codeCache != null && name != null) { - userCache = UserCache(name, photo) - _isAuthorized.value = true - } + if (codeCache != null && name != null) { + userCache = UserCache(name, photo) + _isAuthorized.value = true } } - private fun requireContext(): Context { - return _context ?: throw IllegalStateException( - "AuthRepository not inited" - ) - } - - private fun getPrefs() = requireContext().getSharedPreferences(PREFS_NAME, MODE_PRIVATE) + private fun getPrefs() = context.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) suspend fun checkAndSave(text: String): Result { return NetworkDataSource.checkAuth(text).onSuccess { success -> diff --git a/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt index f8049bd..eb44a43 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt @@ -4,9 +4,10 @@ import ru.myitschool.work.data.model.BookingInfoResponse import ru.myitschool.work.data.source.AuthException import ru.myitschool.work.data.source.NetworkDataSource -object BookRepository { +class BookRepository(private val authRepository: AuthRepository) { + suspend fun getAvailableBookings(): Result { - val code = AuthRepository.getCurrentCode() + val code = authRepository.getCurrentCode() return if (code != null) { NetworkDataSource.getBookingInfo(code) } else { @@ -15,7 +16,7 @@ object BookRepository { } suspend fun book(date: String, placeId: Int): Result { - val code = AuthRepository.getCurrentCode() + val code = authRepository.getCurrentCode() return if (code != null) { NetworkDataSource.book(code, date, placeId) } else { diff --git a/app/src/main/java/ru/myitschool/work/data/repo/MainRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/MainRepository.kt index 9108be3..f59d299 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/MainRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/MainRepository.kt @@ -4,9 +4,10 @@ import ru.myitschool.work.data.model.UserInfoResponse import ru.myitschool.work.data.source.AuthException import ru.myitschool.work.data.source.NetworkDataSource -object MainRepository { +class MainRepository(private val authRepository: AuthRepository) { + suspend fun getUserInfo(): Result { - val code = AuthRepository.getCurrentCode() + val code = authRepository.getCurrentCode() return if (code != null) { NetworkDataSource.getInfo(code) } else { diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt b/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt index b82c7f0..f8abc91 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt @@ -6,7 +6,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -24,8 +26,10 @@ fun AppNavHost( modifier: Modifier = Modifier, navController: NavHostController = rememberNavController() ) { - // authorized state follow - val isAuthorized by AuthRepository.isAuthorized.collectAsState() + val context = LocalContext.current + val authRepository = remember { AuthRepository.getInstance(context) } + + val isAuthorized by authRepository.isAuthorized.collectAsState() LaunchedEffect(isAuthorized) { if (isAuthorized) { diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt index 82a8afc..fd5d0df 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -34,7 +35,7 @@ import ru.myitschool.work.ui.nav.MainScreenDestination @Composable fun AuthScreen( - viewModel: AuthViewModel = viewModel(), + viewModel: AuthViewModel = viewModel(factory = AuthViewModelFactory(LocalContext.current)), navController: NavController ) { val state by viewModel.uiState.collectAsState() diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt index e24e8e3..8df8756 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt @@ -1,6 +1,8 @@ package ru.myitschool.work.ui.screen.auth +import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -12,10 +14,11 @@ import kotlinx.coroutines.launch import ru.myitschool.work.data.repo.AuthRepository 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.Data()) val uiState: StateFlow = _uiState.asStateFlow() @@ -68,6 +71,18 @@ class AuthViewModel : ViewModel() { } } + +class AuthViewModelFactory(private val context: Context) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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 { object NavigateToMain : AuthAction } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt index 3bba494..b3cdb4c 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt @@ -19,13 +19,14 @@ import androidx.navigation.NavController import ru.myitschool.work.R import ru.myitschool.work.core.TestIds import androidx.compose.material3.Icon +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign @OptIn(ExperimentalMaterial3Api::class) @Composable fun BookScreen( - viewModel: BookViewModel = viewModel(), + viewModel: BookViewModel = viewModel(factory = BookViewModelFactory(LocalContext.current)), navController: NavController ) { val state by viewModel.uiState.collectAsState() diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt index 536143c..9b8e101 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt @@ -1,6 +1,8 @@ package ru.myitschool.work.ui.screen.book +import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -9,14 +11,14 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.data.repo.BookRepository import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -private val bookRepo = BookRepository -class BookViewModel : ViewModel() { +class BookViewModel(private val bookRepo: BookRepository) : ViewModel() { private val _uiState = MutableStateFlow(BookState.Loading) val uiState: StateFlow = _uiState.asStateFlow() @@ -179,4 +181,16 @@ class BookViewModel : ViewModel() { sealed interface BookAction { object NavigateBack : BookAction object NavigateBackWithRefresh : BookAction +} + + +class BookViewModelFactory(private val context: Context) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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") + } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt index 2f9d4a7..c501629 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt @@ -46,7 +46,7 @@ import ru.myitschool.work.ui.nav.BookScreenDestination @Composable fun MainScreen( - viewModel: MainViewModel = viewModel(), + viewModel: MainViewModel = viewModel(factory = MainViewModelFactory(LocalContext.current)), navController: NavController ) { val state by viewModel.uiState.collectAsState() diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt index 7e576a4..5e75bd2 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt @@ -1,6 +1,8 @@ package ru.myitschool.work.ui.screen.main +import android.content.Context import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow 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.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.Loading) val uiState: StateFlow = _uiState.asStateFlow() @@ -95,6 +98,17 @@ class MainViewModel : ViewModel() { } } +class MainViewModelFactory(private val context: Context) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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 { object NavigateToAuth : MainAction object NavigateToBooking : MainAction