Внедрение даггера

This commit is contained in:
2023-04-22 21:19:31 +03:00
parent 8b06ccdaa2
commit 09800f9567
20 changed files with 301 additions and 112 deletions
+18 -10
View File
@@ -1,23 +1,31 @@
package ru.megboyzz.dnevnik
import android.app.Application
import android.content.Context
import androidx.room.Room
import ru.megboyzz.dnevnik.db.AppDataBase
import ru.megboyzz.dnevnik.di.AppComponent
import ru.megboyzz.dnevnik.di.DaggerAppComponent
import ru.megboyzz.dnevnik.di.modules.AppModule
class App : Application() {
lateinit var database: AppDataBase
private set
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
instance = this
database = Room.databaseBuilder(this, AppDataBase::class.java, "database")
appComponent = DaggerAppComponent
.builder()
.appModule(AppModule(this))
.build()
}
}
companion object {
var instance: App? = null
}
}
/*
* Специальное расширение для получения AppComponent в любом месте, где у вас есть доступ к Context.
* Позволяет избегать статического хранения ссылки на ваш [Application] класс
*/
val Context.appComponent: AppComponent
get() = when (this) {
is App -> appComponent
else -> applicationContext.appComponent
}
@@ -1,40 +1,32 @@
package ru.megboyzz.dnevnik
import android.app.Application
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ReportFragment.Companion.reportFragment
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Jsoup
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.navigation.AppNavHost
import ru.megboyzz.dnevnik.ui.theme.lightGray
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
import java.io.IOException
import java.net.URL
import javax.inject.Inject
import kotlin.concurrent.thread
class MainActivity : ComponentActivity() {
lateinit var app: App
@Inject
lateinit var credentialsDao: CredentialsDao
@Inject
lateinit var globalUserContext: GlobalUserContextDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.app = application as App
app.appComponent.inject(this)
setContent {
Surface(
@@ -46,15 +38,10 @@ class MainActivity : ComponentActivity() {
}
}
@OptIn(DelicateCoroutinesApi::class)
override fun onDestroy() {
super.onDestroy()
GlobalScope.launch(Dispatchers.IO) {
val credentialsDao = app.database.credentialsDao()
val globalUserContext = app.database.globalUserContextDao()
thread {
if(credentialsDao.credentialsIsExists()) {
val credentials = credentialsDao.getCredentials()
if (!credentials.rememberMe) {
@@ -62,8 +49,6 @@ class MainActivity : ComponentActivity() {
globalUserContext.deleteGlobalUserContext()
}
}
}
}
@@ -5,40 +5,30 @@ import com.google.gson.Gson
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.Connection
import org.jsoup.Jsoup
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.megboyzz.dnevnik.db.AppDataBase
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.di.DaggerAppComponent
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
import ru.megboyzz.dnevnik.entities.context.marks.init.state.SchoolType
import ru.megboyzz.dnevnik.entities.db.Credentials
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
import ru.megboyzz.dnevnik.service.ContextService
import java.net.URL
import javax.inject.Inject
class AuthorizationManager(dataBase: AppDataBase) {
class AuthorizationManager @Inject constructor(
val contextService: ContextService,
val credentialsDao: CredentialsDao,
val globalUserContextDao: GlobalUserContextDao
) {
private val returnUrl =
"https://login.dnevnik.ru/oauth2?response_type=token&client_id=bb97b3e445a340b9b9cab4b9ea0dbd6f&scope=CommonInfo,ContactInfo,FriendsAndRelatives,EducationalInfo"
private val baseUrl = "https://login.dnevnik.ru/login/"
private val apiUrl = "https://api.dnevnik.ru"
private val retrofit = Retrofit
.Builder()
.baseUrl(apiUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
private val contextService = retrofit.create(ContextService::class.java)
private val marksState = "https://dnevnik.ru/marks"
private val credentialsDao = dataBase.credentialsDao()
private val userContextDao = dataBase.globalUserContextDao()
private var credentials = Credentials(token = "", cookies = mapOf(), rememberMe = false)
private val gson = Gson()
@@ -104,7 +94,7 @@ class AuthorizationManager(dataBase: AppDataBase) {
if(context.isSuccessful){
val body = context.body() ?: return AuthorizationStatus.UNKNOWN_ERROR
userContextDao.setGlobalUserContext(GlobalUserContext(
globalUserContextDao.setGlobalUserContext(GlobalUserContext(
apiUserContext = body,
marksInitStateUserContext = marksContext
))
@@ -0,0 +1,35 @@
package ru.megboyzz.dnevnik.di
import android.app.Activity
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import dagger.BindsInstance
import dagger.Component
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.di.modules.AppModule
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
import javax.inject.Singleton
@Singleton
@Component(modules = [AppModule::class])
interface AppComponent {
fun inject(viewModel: AuthorizationViewModel)
fun inject(viewModel: ProfileViewModel)
fun inject(activity: MainActivity)
fun getCredentialsDao(): CredentialsDao
fun getGlobalUserContextDao(): GlobalUserContextDao
@Component.Builder
interface Builder {
fun appModule(appModule: AppModule): Builder // add this
fun build(): AppComponent
}
}
@@ -0,0 +1,28 @@
package ru.megboyzz.dnevnik.di.modules
import dagger.Module
import dagger.Provides
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.megboyzz.dnevnik.service.ContextService
@Module
class ApiModule {
private val apiUrl = "https://api.dnevnik.ru"
@Provides
fun provideApiRetrofit(): Retrofit{
return Retrofit
.Builder()
.baseUrl(apiUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
fun provideContextService(retrofit: Retrofit): ContextService{
return retrofit.create(ContextService::class.java)
}
}
@@ -0,0 +1,20 @@
package ru.megboyzz.dnevnik.di.modules
import android.app.Application
import android.content.Context
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module(includes = [ApiModule::class, DatabaseModule::class, AuthModule::class])
class AppModule(private val application: Application) {
@Provides
@Singleton
fun provideContext(): Context = this.application.applicationContext
@Provides
@Singleton
fun provideApplication(): Application = application
}
@@ -0,0 +1,15 @@
package ru.megboyzz.dnevnik.di.modules
import dagger.Module
import dagger.Provides
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.service.ContextService
@Module
class AuthModule {
}
@@ -0,0 +1,31 @@
package ru.megboyzz.dnevnik.di.modules
import android.content.Context
import androidx.room.Room
import dagger.Module
import dagger.Provides
import ru.megboyzz.dnevnik.db.AppDataBase
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import javax.inject.Singleton
@Module
class DatabaseModule {
@Singleton
@Provides
fun provideDatabase(context: Context): AppDataBase {
return Room
.databaseBuilder(context, AppDataBase::class.java, "database")
.build()
}
@Provides
fun provideCredentialsDao(dataBase: AppDataBase): CredentialsDao = dataBase.credentialsDao()
@Provides
fun provideGlobalUserContextDao(dataBase: AppDataBase): GlobalUserContextDao = dataBase.globalUserContextDao()
}
@@ -3,23 +3,19 @@ package ru.megboyzz.dnevnik.navigation
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.screens.*
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
import ru.megboyzz.dnevnik.navigate
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
sealed class BaseNavRote(open val route: String)
@@ -1,13 +1,22 @@
package ru.megboyzz.dnevnik.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.MainActivity
import ru.megboyzz.dnevnik.appComponent
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import ru.megboyzz.dnevnik.collectAsMutableState
import ru.megboyzz.dnevnik.navigate
@@ -15,17 +24,14 @@ import ru.megboyzz.dnevnik.navigation.AppNavRoute
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModelFactory
//TODO Переверстать экран входа
@Composable
fun LoginScreen(navController: NavController) {
val app = (LocalContext.current as MainActivity).app
val context = LocalContext.current
val authorizationViewModel: AuthorizationViewModel = viewModel(
factory = AuthorizationViewModelFactory(app)
)
val authorizationViewModel: AuthorizationViewModel = viewModel()
val login = authorizationViewModel.login.collectAsMutableState()
@@ -53,22 +59,18 @@ fun LoginScreen(navController: NavController) {
)
}
if(loginStatus.value == AuthorizationStatus.NO_INTERNET){
AlertMessageBox(
title = "Информация",
alertText = "Отсутствует подключение к сети",
onClick = { loginStatus.value = AuthorizationStatus.NOTHING }
)
}
if(loginStatus.value == AuthorizationStatus.LOGGED)
navController.navigate(AppNavRoute.Marks)
Column() {
Button(onClick = {
login.value = "08012002@mail.ru"
password.value = "1282565121024QWEasdfZXC"
}) {
Text("Я")
}
Button(onClick = {
login.value = "kitsekaterina"
password.value = "k12345"
}) {
Text("Катя")
}
Box(){
LoginScreenContent(
login = login.value,
onLoginChange = { login.value = it },
@@ -91,5 +93,35 @@ fun LoginScreen(navController: NavController) {
onSignUp = { /*TODO*/ },
onForgetPassword = { /* TODO */ }
)
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)){
Button(onClick = {
login.value = "08012002@mail.ru"
password.value = "1282565121024QWEasdfZXC"
}) {
Text("Я")
}
Button(onClick = {
login.value = "kitsekaterina"
password.value = "k12345"
}) {
Text("Катя")
}
val scope = rememberCoroutineScope()
var isRemoving by remember{ mutableStateOf(false) }
Button(onClick = {
val globalUserContextDao = context.appComponent.getGlobalUserContextDao()
val credentialsDao = context.appComponent.getCredentialsDao()
scope.launch(Dispatchers.IO) {
isRemoving = true
globalUserContextDao.deleteGlobalUserContext()
credentialsDao.deleteCredentials()
isRemoving = false
}
}) {
Text("Удалить данные входа")
}
if(isRemoving) CircularProgressIndicator()
}
}
}
@@ -0,0 +1,11 @@
package ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model
import java.util.*
data class MarkModel(
val subjectName: String,
val mark: Int,
val date: Date,
val teacher: String,
val reason: String = "null"
)
@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.*
import coil.compose.AsyncImage
import ru.megboyzz.dnevnik.*
import ru.megboyzz.dnevnik.R
import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel
import ru.megboyzz.dnevnik.ui.theme.*
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
@@ -364,11 +365,7 @@ fun CardWithAverage(
@Composable
fun LastMarkCard(
subjectName: String,
mark: Int,
date: String, /* мейби стоит поменять на Date или инстанс */
teacher: String,
reason: String = "null"
markModel: MarkModel
) {
BaseCard {
Row(
@@ -381,29 +378,29 @@ fun LastMarkCard(
modifier = Modifier.width(200.dp)
) {
Text(
text = subjectName,
text = markModel.subjectName,
style = MainText
)
Text(
text = mark.toString(),
text = markModel.mark.toString(),
style = H1,
color = mainBlue
)
}
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(
text = "${R.string.title_mark_recive.AsString()} $date",
text = "${R.string.title_mark_recive.AsString()} ${markModel.date}",
style = MainText,
color = dark
)
Text(
text = "${R.string.title_teacher_name.AsString()}: $teacher",
text = "${R.string.title_teacher_name.AsString()}: ${markModel.teacher}",
style = MainText,
color = dark
)
if(reason != "null")
if(markModel.reason != "null")
Text(
text = "${R.string.title_reason.AsString()} $reason",
text = "${R.string.title_reason.AsString()} ${markModel.reason}",
style = MainText,
color = dark
)
@@ -171,17 +171,6 @@ fun PieChartCardPrev() {
)
}
@Preview
@Composable
fun LastMarkCardPrev() {
LastMarkCard(
subjectName = "Русский язык",
mark = 5,
date = "18.02.2023",
teacher = "Степанова А.С.",
reason = "контрольная"
)
}
@Preview
@Composable
@@ -91,7 +91,7 @@ fun DrawerContent(
val profile = profileViewModel.profile.collectAsState()
val dao = app.database.credentialsDao()
val dao = LocalContext.current.appComponent.getCredentialsDao()
var leaveAlertIsOpened by remember {
@@ -129,7 +129,6 @@ fun DrawerContent(
.background(mainBlue),
horizontalAlignment = Alignment.Start,
) {
SpacerHeight(10.dp)
if(profile.value == null)
CircularProgressIndicator()
else
@@ -12,18 +12,21 @@ import ru.megboyzz.dnevnik.App
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
import java.lang.IllegalArgumentException
import javax.inject.Inject
//Если есть попадание в этот вьюмодел то нам нужна авторизация
class AuthorizationViewModel(application: App) : AndroidViewModel(application) {
class AuthorizationViewModel(val application: App): AndroidViewModel(application) {
val login = MutableStateFlow("")
val password = MutableStateFlow("")
val rememberMe = MutableStateFlow(false)
@Inject
lateinit var authManager: AuthorizationManager
val loginStatus = MutableStateFlow(AuthorizationStatus.CREDENTIALS_LOADING)
private val authManager = AuthorizationManager(application.database)
init {
application.appComponent.inject(this)
viewModelScope.launch(Dispatchers.IO) {
loginStatus.emit(authManager.checkCredentials())
}
@@ -44,7 +47,6 @@ class AuthorizationViewModel(application: App) : AndroidViewModel(application) {
}
class AuthorizationViewModelFactory(private val application: App) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@@ -8,19 +8,23 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.App
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
import ru.megboyzz.dnevnik.entities.context.GroupType
import ru.megboyzz.dnevnik.entities.context.SchoolType
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
import java.lang.IllegalArgumentException
import javax.inject.Inject
class ProfileViewModel(application: App) : AndroidViewModel(application) {
class ProfileViewModel(val application: App) : AndroidViewModel(application) {
val profile = MutableStateFlow<UserProfileModel?>(null)
val userContextDao = application.database.globalUserContextDao()
@Inject
lateinit var userContextDao: GlobalUserContextDao
init{
application.appComponent.inject(this)
viewModelScope.launch(Dispatchers.IO) {
val globalUserContext = userContextDao.getGlobalUserContext()
val convertedProfile = convertGlobalUserContextToProfile(globalUserContext)
@@ -30,7 +34,7 @@ class ProfileViewModel(application: App) : AndroidViewModel(application) {
private fun convertGlobalUserContextToProfile(
globalUserContext: GlobalUserContext
): UserProfileModel{
): UserProfileModel {
val info = globalUserContext.apiUserContext.info
val personContext = globalUserContext.apiUserContext.contextPersons.find {
@@ -0,0 +1,14 @@
package ru.megboyzz.dnevnik.viewmodel.core
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import ru.megboyzz.dnevnik.App
import java.lang.IllegalArgumentException
class ViewModelFactory(private val application: App): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ViewModel::class.java))
return modelClass.getConstructor().newInstance(application)
throw IllegalArgumentException("Unknown ViewModel Class")
}
}
@@ -0,0 +1,28 @@
package ru.megboyzz.dnevnik.viewmodel.subscreen
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import ru.megboyzz.dnevnik.App
import ru.megboyzz.dnevnik.entities.response.UserContext
import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel
import java.lang.IllegalArgumentException
class LastMarksViewModel(apiUserContext: UserContext): ViewModel() {
val lastMarksList = MutableStateFlow(listOf<MarkModel>())
val isLoading = MutableStateFlow(true)
init{
viewModelScope.launch(Dispatchers.IO) {
}
}
}
+5 -1
View File
@@ -37,6 +37,8 @@ object AppLibraries: DependencyList() {
val roomKtx = "androidx.room:room-ktx:${Versions.room}"
val jsoup = "org.jsoup:jsoup:1.15.4"
val dagger = "com.google.dagger:dagger:${Versions.daggerVersion}"
}
@@ -69,7 +71,9 @@ object TestLibraries: DependencyList() {
object KaptLibraries: DependencyList(){
val compiler = "androidx.room:room-compiler:${Versions.room}"
val roomCompiler = "androidx.room:room-compiler:${Versions.room}"
val daggerCompiler = "com.google.dagger:dagger-compiler:${Versions.daggerVersion}"
}
/**
+1
View File
@@ -14,6 +14,7 @@ object Versions {
val compose = "1.4.1"
val room = "2.2.6"
val daggerVersion = "2.45"
//test
val junit = "4.12"