diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index fb7f4a8..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 8632f65..0000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 44ca2d9..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index cfc391f..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index c49e1e9..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ResultHandler/MainActivity.java b/ResultHandler/MainActivity.java new file mode 100644 index 0000000..e89ffda --- /dev/null +++ b/ResultHandler/MainActivity.java @@ -0,0 +1,35 @@ +package com.example.myapplication; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.Toast; + +public class MainActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + } + + public void onClick(View v){ + Intent intent = new Intent(this, PuppetActivity.class); + ResultHandler.addResultHandler(intent, () -> { + Toast.makeText(this, "lol!", Toast.LENGTH_LONG).show(); + }); + startActivityForResult(intent, 0); + //ResultHandler handler = new ResultHandler(this); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + ResultHandler handler = new ResultHandler(this); + handler.onIncomingIntent(requestCode, resultCode, data); + } +} \ No newline at end of file diff --git a/ResultHandler/PuppetActivity.java b/ResultHandler/PuppetActivity.java new file mode 100644 index 0000000..8207554 --- /dev/null +++ b/ResultHandler/PuppetActivity.java @@ -0,0 +1,14 @@ +package com.example.myapplication; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +public class PuppetActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pupet); + } +} \ No newline at end of file diff --git a/ResultHandler/ResultAction.java b/ResultHandler/ResultAction.java new file mode 100644 index 0000000..d5aaef9 --- /dev/null +++ b/ResultHandler/ResultAction.java @@ -0,0 +1,5 @@ +package com.example.myapplication; + +public interface ResultAction { + void action(); +} diff --git a/ResultHandler/ResultHandler.java b/ResultHandler/ResultHandler.java new file mode 100644 index 0000000..5e8ba8d --- /dev/null +++ b/ResultHandler/ResultHandler.java @@ -0,0 +1,56 @@ +package com.example.myapplication; + +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class ResultHandler { + + private Context context; + private static Map map = new HashMap<>(); + private static final int RANDOM_BOUNDS = 256; + private static final String LOG_TAG = "ResultHandler"; + + public static final String ACTION_ID_TITLE = "ACTION_ID"; + + public ResultHandler(Context context) { + this.context = context; + } + + /** + * Статические поля кодов обработки входящих интентов + */ + public enum Fields{ + FILE_OPEN, + } + + public static void addResultHandler(Intent intent, ResultAction action){ + int random = (new Random()).nextInt(RANDOM_BOUNDS); + intent.putExtra(ACTION_ID_TITLE, random); + map.put(random, action); + + } + + public void onIncomingIntent(int requestCode, int resultCode, Intent data){ + if(data != null){ + if(data.hasExtra(ACTION_ID_TITLE)){ + int id = data.getExtras().getInt(ACTION_ID_TITLE); + map.get(id).action(); + }else{ + Log.e(LOG_TAG, "Incoming intent haven't got ACTION_ID!!"); + throw new RuntimeException( + "ResultHandler::RuntimeException can't parse incoming intent! Shutting down((" + ); + } + }else{ + Log.e(LOG_TAG, "Incoming intent is null!!"); + throw new RuntimeException( + "ResultHandler::RuntimeException can't parse incoming intent! Shutting down((" + ); + } + } +} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0605ae0..7e3caa8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,11 +32,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" freeCompilerArgs = listOf( "-Xjvm-default=compatibility" ) @@ -57,8 +57,20 @@ android { } -tasks.register("connectToWSA"){ +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = "11" + } +} +tasks.register("connectToWSA") + +tasks { + named("connectToWSA") { + exec { + commandLine = "adb connect localhost:58526".split(" ") + } + } } dependencies { diff --git a/app/src/main/java/ru/megboyzz/dnevnik/authorization/AuthorizationManager.kt b/app/src/main/java/ru/megboyzz/dnevnik/authorization/AuthorizationManager.kt index 6661e2e..ab079bf 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/authorization/AuthorizationManager.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/authorization/AuthorizationManager.kt @@ -4,6 +4,7 @@ import android.util.Log import com.google.gson.Gson import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.jsoup.Connection +import org.jsoup.HttpStatusException import org.jsoup.Jsoup import ru.megboyzz.dnevnik.db.dao.CredentialsDao import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao @@ -49,7 +50,12 @@ class AuthorizationManager @Inject constructor( .followRedirects(true) .execute() }.onFailure { + + if(it is HttpStatusException && it.statusCode == 405) + return AuthorizationStatus.MAINTAIN + return AuthorizationStatus.NO_INTERNET + }.onSuccess { response -> if(response.statusCode() != 200) return AuthorizationStatus.MAINTAIN diff --git a/app/src/main/java/ru/megboyzz/dnevnik/di/AppComponent.kt b/app/src/main/java/ru/megboyzz/dnevnik/di/AppComponent.kt index 2bfd2c3..6ef3036 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/di/AppComponent.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/di/AppComponent.kt @@ -13,6 +13,7 @@ import ru.megboyzz.dnevnik.di.modules.AppModule import ru.megboyzz.dnevnik.entities.db.Credentials import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel +import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel import javax.inject.Singleton @@ -24,6 +25,8 @@ interface AppComponent { val profileViewModel: ProfileViewModel + val lastMarksViewModel: LastMarksViewModel + val credentialsDao: CredentialsDao val globalUserContextDao: GlobalUserContextDao diff --git a/app/src/main/java/ru/megboyzz/dnevnik/di/modules/ApiModule.kt b/app/src/main/java/ru/megboyzz/dnevnik/di/modules/ApiModule.kt index 3a9ee3b..7352c9f 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/di/modules/ApiModule.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/di/modules/ApiModule.kt @@ -1,10 +1,17 @@ package ru.megboyzz.dnevnik.di.modules +import com.google.gson.Gson +import com.google.gson.GsonBuilder import dagger.Module import dagger.Provides import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.create import ru.megboyzz.dnevnik.service.ContextService +import ru.megboyzz.dnevnik.service.MarksService +import ru.megboyzz.dnevnik.util.gson.DateDeserializer +import ru.megboyzz.dnevnik.util.gson.DateSerializer +import java.util.* @Module class ApiModule { @@ -12,17 +19,24 @@ class ApiModule { private val apiUrl = "https://api.dnevnik.ru" @Provides - fun provideApiRetrofit(): Retrofit{ + fun provideApiRetrofit(gson: Gson): Retrofit{ return Retrofit .Builder() .baseUrl(apiUrl) - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) .build() } @Provides - fun provideContextService(retrofit: Retrofit): ContextService{ - return retrofit.create(ContextService::class.java) - } + fun provideGson(): Gson = GsonBuilder() + //.registerTypeAdapter(Date::class.java, DateSerializer()) + .registerTypeAdapter(Date::class.java, DateDeserializer()) + .create() + + @Provides + fun provideContextService(retrofit: Retrofit): ContextService = retrofit.create() + + @Provides + fun provideMarksService(retrofit: Retrofit): MarksService = retrofit.create() } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodGroup.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodGroup.kt index be64bb7..09f542d 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodGroup.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodGroup.kt @@ -2,6 +2,6 @@ package ru.megboyzz.dnevnik.entities.context data class PeriodGroup( val id: Long, - val type: GroupType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них???? + val type: PeriodType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них???? val periods: List ) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodInfo.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodInfo.kt index 158ee6f..78a2ea5 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodInfo.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodInfo.kt @@ -3,7 +3,7 @@ package ru.megboyzz.dnevnik.entities.context data class PeriodInfo( val id: Long, val number: Short, - val type: GroupType, + val type: PeriodType, val dateStart: Long, val dateFinish: Long, val studyYear: Short, diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/GroupType.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodType.kt similarity index 71% rename from app/src/main/java/ru/megboyzz/dnevnik/entities/context/GroupType.kt rename to app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodType.kt index d2fd661..832884a 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/entities/context/GroupType.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/context/PeriodType.kt @@ -1,5 +1,5 @@ package ru.megboyzz.dnevnik.entities.context -enum class GroupType { +enum class PeriodType { Quarter } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/lessons/Lesson.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/lessons/Lesson.kt new file mode 100644 index 0000000..93fd614 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/lessons/Lesson.kt @@ -0,0 +1,18 @@ +package ru.megboyzz.dnevnik.entities.lessons + +import com.google.gson.annotations.SerializedName +import java.util.* + +data class Lesson( + val id: Long, + val title: String, + val date: Date, + val number: Int, + val subjectId: Long, + val place: String, + val hours: String, + @SerializedName("works") + val workIds: List, + @SerializedName("teachers") + val teachersId: List + ) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/Mark.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/Mark.kt new file mode 100644 index 0000000..ba9455c --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/Mark.kt @@ -0,0 +1,18 @@ +package ru.megboyzz.dnevnik.entities.marks + +import com.google.gson.annotations.SerializedName +import java.util.* + +data class Mark( + val id: Long, + val value: String, + @SerializedName("person") + val personId: Long, + @SerializedName("work") + val workId: Long, + @SerializedName("lesson") + val lessonId: Long, + val number: Int, + val date: Date, + val workType: Long +) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/RecentMarks.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/RecentMarks.kt new file mode 100644 index 0000000..a01e241 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/marks/RecentMarks.kt @@ -0,0 +1,14 @@ +package ru.megboyzz.dnevnik.entities.marks + +import ru.megboyzz.dnevnik.entities.lessons.Lesson +import ru.megboyzz.dnevnik.entities.subjects.Subject +import ru.megboyzz.dnevnik.entities.works.Work +import ru.megboyzz.dnevnik.entities.works.WorkType + +data class RecentMarks( + val marks: List, + val works: List, + val subjects: List, + val workTypes: List, + val lessons: List, +) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/subjects/Subject.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/subjects/Subject.kt new file mode 100644 index 0000000..c79be40 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/subjects/Subject.kt @@ -0,0 +1,6 @@ +package ru.megboyzz.dnevnik.entities.subjects + +data class Subject( + val id: Long, + val name: String +) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/Teacher.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/Teacher.kt new file mode 100644 index 0000000..f02dfa4 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/Teacher.kt @@ -0,0 +1,12 @@ +package ru.megboyzz.dnevnik.entities.teachers + +data class Teacher( + val id: Long, + val userId: Long, + val firstName: String, + val lastName: String, + val middleName: String, + val sex: TeacherSex, +){ + override fun toString() = "$lastName ${firstName.first()}. ${middleName.first()}." +} diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/TeacherSex.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/TeacherSex.kt new file mode 100644 index 0000000..92d18a9 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/teachers/TeacherSex.kt @@ -0,0 +1,6 @@ +package ru.megboyzz.dnevnik.entities.teachers + +enum class TeacherSex { + Male, + Female +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/works/Work.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/works/Work.kt new file mode 100644 index 0000000..1eb16e9 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/works/Work.kt @@ -0,0 +1,23 @@ +package ru.megboyzz.dnevnik.entities.works + +import com.google.gson.annotations.SerializedName +import java.util.* + +data class Work( + val id: Long, + val workType: Long, + val markCount: Int, + @SerializedName("lesson") + val lessonId: Long, + val displayInJournal: Boolean, + @SerializedName("eduGroup") + val eduGroupId: Long, + val text: String, + val periodNumber: Int, + val periodType: String, + val subjectId: Long, + val isImportant: Boolean, + val targetDate: Date, + val sentDate: Date?, + val files: List? +) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/entities/works/WorkType.kt b/app/src/main/java/ru/megboyzz/dnevnik/entities/works/WorkType.kt new file mode 100644 index 0000000..365dcf7 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/entities/works/WorkType.kt @@ -0,0 +1,8 @@ +package ru.megboyzz.dnevnik.entities.works + +data class WorkType( + val id: Long, + val schoolId: Long, + val abbreviation: String, + val name: String +) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/navigation/AppNavHost.kt b/app/src/main/java/ru/megboyzz/dnevnik/navigation/AppNavHost.kt index 1850866..826e634 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/navigation/AppNavHost.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/navigation/AppNavHost.kt @@ -10,12 +10,12 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController 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.navigate import androidx.activity.viewModels import ru.megboyzz.dnevnik.appComponent +import ru.megboyzz.dnevnik.util.collectAsMutableState +import ru.megboyzz.dnevnik.util.navigate import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory sealed class BaseNavRote(open val route: String) diff --git a/app/src/main/java/ru/megboyzz/dnevnik/navigation/MarksSubScreenNavHost.kt b/app/src/main/java/ru/megboyzz/dnevnik/navigation/MarksSubScreenNavHost.kt index c3bd285..2e87085 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/navigation/MarksSubScreenNavHost.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/navigation/MarksSubScreenNavHost.kt @@ -10,9 +10,10 @@ import androidx.compose.runtime.remember import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.screens.* +import ru.megboyzz.dnevnik.screens.subscreens.LastMarksSubScreen import ru.megboyzz.dnevnik.screens.ui.SubScreenData import ru.megboyzz.dnevnik.screens.ui.SubScreenNavBar @@ -26,7 +27,6 @@ sealed class MarksNavRoute(override val route: String): BaseNavRote(route){ object ByYearMarks: MarksNavRoute("by_terms_marks") } -@OptIn(ExperimentalAnimationApi::class) @Composable fun MarksSubScreenNavHost() { @@ -77,7 +77,7 @@ fun MarksSubScreenNavHost() { navController = navController, startDestination = MarksNavRoute.LastMarks.route ) { - composable(MarksNavRoute.LastMarks.route) { Text(MarksNavRoute.LastMarks.route) } + composable(MarksNavRoute.LastMarks.route) { LastMarksSubScreen() } composable(MarksNavRoute.ByDaysMarks.route) { Text(MarksNavRoute.ByDaysMarks.route) } composable(MarksNavRoute.StatsByMarks.route) { Text(MarksNavRoute.StatsByMarks.route) } composable(MarksNavRoute.StatsBySubjects.route) { Text(MarksNavRoute.StatsBySubjects.route) } diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/HomeWorkScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/HomeWorkScreen.kt index f589d75..372d176 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/HomeWorkScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/HomeWorkScreen.kt @@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens import androidx.compose.material.ScaffoldState import androidx.compose.runtime.Composable import androidx.navigation.NavController -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/LoginScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/LoginScreen.kt index 9fd1d44..a29a08e 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/LoginScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/LoginScreen.kt @@ -17,8 +17,8 @@ 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 +import ru.megboyzz.dnevnik.util.collectAsMutableState +import ru.megboyzz.dnevnik.util.navigate import ru.megboyzz.dnevnik.navigation.AppNavRoute import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent @@ -65,7 +65,17 @@ fun LoginScreen(navController: NavController) { ) } + if(loginStatus.value == AuthorizationStatus.MAINTAIN){ + isLoading = false + AlertMessageBox( + title = "Ведутся профилактические работы", + alertText = "Прямо сейчас мы проводим важные обновления, чтобы стать ещё лучше и удобнее. Пожалуйста, проявите немного терпения!", + onClick = { authorizationViewModel.acceptAlert() } + ) + } + if(loginStatus.value == AuthorizationStatus.NO_INTERNET){ + isLoading = false AlertMessageBox( title = "Информация", alertText = "Отсутствует подключение к сети", diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/MarksScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/MarksScreen.kt index 8d93a73..d9a4c9b 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/MarksScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/MarksScreen.kt @@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens import androidx.compose.material.ScaffoldState import androidx.compose.runtime.Composable import androidx.navigation.NavController -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.navigation.MarksSubScreenNavHost import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ScheduleScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ScheduleScreen.kt index adcf070..85a92a4 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ScheduleScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ScheduleScreen.kt @@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens import androidx.compose.material.ScaffoldState import androidx.compose.runtime.Composable import androidx.navigation.NavController -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/SettingsScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/SettingsScreen.kt index cf89f3c..aecd166 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/SettingsScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/SettingsScreen.kt @@ -16,6 +16,7 @@ import ru.megboyzz.dnevnik.navigation.AppNavRoute import ru.megboyzz.dnevnik.screens.ui.UnderlinedText import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.util.* @Composable fun SettingsScreen( diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/ByDayMarksScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/ByDayMarksScreen.kt index 9c8d36a..9ab4fc2 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/ByDayMarksScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/ByDayMarksScreen.kt @@ -1,2 +1,9 @@ package ru.megboyzz.dnevnik.screens.subscreens +import androidx.compose.runtime.Composable + +@Composable +fun ByDayMarksScreen() { + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/LastMarksSubScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/LastMarksSubScreen.kt index 9c8d36a..8c0c1ee 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/LastMarksSubScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/LastMarksSubScreen.kt @@ -1,2 +1,57 @@ package ru.megboyzz.dnevnik.screens.subscreens +import androidx.activity.viewModels +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import ru.megboyzz.dnevnik.MainActivity +import ru.megboyzz.dnevnik.appComponent +import ru.megboyzz.dnevnik.screens.ui.LastMarkCard +import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory +import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel + +@Composable +fun LastMarksSubScreen() { + + val activity = LocalContext.current as MainActivity + + val lastMarksViewModel: LastMarksViewModel by activity.viewModels { + ViewModelFactory{ + activity.appComponent.lastMarksViewModel + } + } + + val isLoading = lastMarksViewModel.isLoading.collectAsState() + + val lastMarks = lastMarksViewModel.lastMarksList.collectAsState() + + if(isLoading.value){ + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ){ + CircularProgressIndicator() + } + }else{ + + LazyColumn{ + + items(lastMarks.value){ + LastMarkCard(markModel = it) + } + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/viewmodel/model/MarkModel.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/viewmodel/model/MarkModel.kt index f90207d..204db77 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/viewmodel/model/MarkModel.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/subscreens/viewmodel/model/MarkModel.kt @@ -4,7 +4,7 @@ import java.util.* data class MarkModel( val subjectName: String, - val mark: Int, + val mark: String, val date: Date, val teacher: String, val reason: String = "null" diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Components.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Components.kt index 5be6e63..c4237ef 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Components.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Components.kt @@ -42,6 +42,10 @@ 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.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString +import ru.megboyzz.dnevnik.util.SpacerHeight +import ru.megboyzz.dnevnik.util.SpacerWidth import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/HomeWorkCard.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/HomeWorkCard.kt index 467f4dd..1d69480 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/HomeWorkCard.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/HomeWorkCard.kt @@ -15,8 +15,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.ui.theme.H2 import ru.megboyzz.dnevnik.ui.theme.dark diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/LoginScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/LoginScreen.kt index 7bbff62..d2ff393 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/LoginScreen.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/LoginScreen.kt @@ -19,6 +19,10 @@ import ru.megboyzz.dnevnik.ui.theme.MainText import ru.megboyzz.dnevnik.ui.theme.Mini import ru.megboyzz.dnevnik.ui.theme.dark import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString +import ru.megboyzz.dnevnik.util.SpacerHeight +import ru.megboyzz.dnevnik.util.SpacerWidth @Composable fun LoginScreenContent( diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Previews.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Previews.kt index 79da5d1..01ab833 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Previews.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/Previews.kt @@ -20,9 +20,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.compose.rememberNavController import com.kizitonwose.calendar.core.daysOfWeek -import ru.megboyzz.dnevnik.navigate +import ru.megboyzz.dnevnik.util.navigate import ru.megboyzz.dnevnik.navigation.AppNavRoute import ru.megboyzz.dnevnik.screens.SettingsScreen +import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel import ru.megboyzz.dnevnik.screens.ui.* import ru.megboyzz.dnevnik.screens.ui.calendar.NiceCalendar import ru.megboyzz.dnevnik.screens.ui.main.DrawerContent @@ -386,8 +387,13 @@ fun Test1() { @Preview @Composable fun AlertTest() { - LeaveAlert(onAgree = { /*TODO*/ }) { - - } + + LastMarkCard(markModel = MarkModel( + subjectName = "Обществознание", + mark = 4.toString(), + teacher = "Расита", + reason = "Ответ на уроке", + date = Date("2023-04-13T00:00:00") + )) } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/SubScreenNavigation.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/SubScreenNavigation.kt index 1d76714..091cdde 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/SubScreenNavigation.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/SubScreenNavigation.kt @@ -16,7 +16,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.navigation.NavController -import ru.megboyzz.dnevnik.navigate +import ru.megboyzz.dnevnik.util.navigate import ru.megboyzz.dnevnik.navigation.BaseNavRote import ru.megboyzz.dnevnik.ui.theme.MainText import ru.megboyzz.dnevnik.ui.theme.dark @@ -27,7 +27,7 @@ import ru.megboyzz.dnevnik.ui.theme.white fun SubScreenNavButton( text: String, isClicked: MutableState, - onCLick: () -> Unit + onClick: () -> Unit ) { val colors = ButtonDefaults.buttonColors( backgroundColor = if(isClicked.value) mainBlue else white, @@ -36,7 +36,7 @@ fun SubScreenNavButton( Button( onClick = { isClicked.value = !isClicked.value - onCLick.invoke() + onClick.invoke() }, shape = RoundedCornerShape(20.dp), border = BorderStroke( @@ -80,8 +80,9 @@ fun SubScreenNavBar( isClicked = data.isClicked ) { for(d in list){ - if(data != d) + if(data != d) { d.isClicked.value = false + } } } } diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt index 18a6682..6e69289 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/calendar/Calendar.kt @@ -26,6 +26,10 @@ import kotlinx.coroutines.launch import ru.megboyzz.dnevnik.* import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.ui.theme.* +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString +import ru.megboyzz.dnevnik.util.rememberFirstMostVisibleMonth +import ru.megboyzz.dnevnik.util.week import java.time.DayOfWeek import java.time.LocalDate import java.time.Month diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/BottomBar.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/BottomBar.kt index afd5a09..07a28e5 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/BottomBar.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/BottomBar.kt @@ -20,6 +20,7 @@ import ru.megboyzz.dnevnik.ui.theme.MainText import ru.megboyzz.dnevnik.ui.theme.mainBlue import ru.megboyzz.dnevnik.ui.theme.white import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.util.* @Composable fun BottomBar(navController: NavController) { diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Drawer.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Drawer.kt index a221e20..1159194 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Drawer.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Drawer.kt @@ -30,6 +30,7 @@ import ru.megboyzz.dnevnik.screens.ui.ProfileCard import ru.megboyzz.dnevnik.ui.theme.H2 import ru.megboyzz.dnevnik.ui.theme.mainBlue import ru.megboyzz.dnevnik.ui.theme.white +import ru.megboyzz.dnevnik.util.* import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Scaffolds.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Scaffolds.kt index b51cf1f..8fb44ca 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Scaffolds.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/main/Scaffolds.kt @@ -16,9 +16,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import kotlinx.coroutines.launch -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString -import ru.megboyzz.dnevnik.SpacerWidth +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString +import ru.megboyzz.dnevnik.util.SpacerWidth import ru.megboyzz.dnevnik.ui.theme.mainBlue import ru.megboyzz.dnevnik.ui.theme.white import ru.megboyzz.dnevnik.R diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/piechart/PieChart.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/piechart/PieChart.kt index 929a582..4da70ee 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/piechart/PieChart.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/piechart/PieChart.kt @@ -2,13 +2,14 @@ package ru.megboyzz.dnevnik.screens.ui.piechart import androidx.compose.foundation.layout.* import androidx.compose.material.Text +import androidx.compose.material.icons.Icons import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.ui.theme.H2 import ru.megboyzz.dnevnik.ui.theme.dark import ru.megboyzz.dnevnik.R diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleCalls.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleCalls.kt index 1127c95..cc5b5a3 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleCalls.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleCalls.kt @@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.screens.ui.BaseCard import ru.megboyzz.dnevnik.ui.theme.H2 diff --git a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleShield.kt b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleShield.kt index f123afc..2fcc892 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleShield.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/screens/ui/schedule/ScheduleShield.kt @@ -8,8 +8,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import ru.megboyzz.dnevnik.AsPainter -import ru.megboyzz.dnevnik.AsString +import ru.megboyzz.dnevnik.util.AsPainter +import ru.megboyzz.dnevnik.util.AsString import ru.megboyzz.dnevnik.R import ru.megboyzz.dnevnik.screens.ui.BaseCard import ru.megboyzz.dnevnik.ui.theme.H2 diff --git a/app/src/main/java/ru/megboyzz/dnevnik/service/ContextService.kt b/app/src/main/java/ru/megboyzz/dnevnik/service/ContextService.kt index cff2fad..af7b602 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/service/ContextService.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/service/ContextService.kt @@ -3,10 +3,17 @@ package ru.megboyzz.dnevnik.service import retrofit2.Call import retrofit2.http.* import ru.megboyzz.dnevnik.entities.response.UserContext +import ru.megboyzz.dnevnik.entities.teachers.Teacher interface ContextService{ @GET("/mobile/v4/users/{personId}/context") fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call + @GET("/v1/edu-groups/{groupId}/teachers") + fun getTeachers( + @Header("Access-Token") accessToken: String, + @Path("groupId") groupId: Long + ): Call> + } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/service/MarksService.kt b/app/src/main/java/ru/megboyzz/dnevnik/service/MarksService.kt index a2687f4..3d6fcec 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/service/MarksService.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/service/MarksService.kt @@ -1,8 +1,22 @@ package ru.megboyzz.dnevnik.service +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path +import ru.megboyzz.dnevnik.entities.marks.RecentMarks + //Сервис для последних оценок interface MarksService { + @GET("/v2/persons/{personId}/group/{groupId}/recentmarks") + fun getRecentMarks( + @Header("Access-Token") accessToken: String, + @Path("personId") personId: Long, + @Path("groupId") groupId: Long + ): Call + + } \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/Utils.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/Utils.kt new file mode 100644 index 0000000..a4d6f1e --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/Utils.kt @@ -0,0 +1,215 @@ +package ru.megboyzz.dnevnik.ui + + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.scale +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.kizitonwose.calendar.compose.CalendarLayoutInfo +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarLayoutInfo +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.core.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.filterNotNull +import ru.megboyzz.dnevnik.ui.theme.dark +import java.time.DayOfWeek +import kotlin.coroutines.* + +@Composable +fun Int.AsPainter() = painterResource(this) + +@Composable +fun Int.AsString() = stringResource(this) + +@Composable +fun SpacerWidth(width: Dp) = Spacer(Modifier.width(width)) + +@Composable +fun SpacerHeight(height: Dp) = Spacer(Modifier.height(height)) + + +@Composable +fun Int.AsImageVector() = ImageVector.vectorResource(this) + + +fun Modifier.drawColoredShadow( + color: Color = Color.Black, + alpha: Float = 0.07f, + borderRadius: Dp = 0.dp, + offsetX: Dp = 0.dp, + offsetY: Dp = 0.dp, + blurRadius: Dp = 7.dp, + spread: Dp = 0.dp, + enabled: Boolean = true, +) = if(enabled) { + this.drawBehind { + val transparentColor = color.copy(alpha = 0.0f).toArgb() + val shadowColor = color.copy(alpha = alpha).toArgb() + this.drawIntoCanvas { + val paint = Paint() + val frameworkPaint = paint.asFrameworkPaint() + frameworkPaint.color = transparentColor + frameworkPaint.setShadowLayer( + blurRadius.toPx(), + offsetX.toPx(), + offsetY.toPx(), + shadowColor + ) + it.save() + + if(spread.value > 0) { + fun calcSpreadScale(spread: Float, childSize: Float): Float { + return 1f + ((spread / childSize) * 2f) + } + + it.scale( + calcSpreadScale(spread.toPx(), this.size.width), + calcSpreadScale(spread.toPx(), this.size.height), + this.center.x, + this.center.y + ) + } + + it.drawRoundRect( + 0f, + 0f, + this.size.width, + this.size.height, + borderRadius.toPx(), + borderRadius.toPx(), + paint + ) + it.restore() + } + } +} else this + +fun Modifier.mainClickable( + radius: Dp = Dp.Unspecified, + onClick: () -> Unit +) = composed { this.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple( + radius = radius, + color = dark + ), + onClick = onClick +) } + +@Composable +fun rememberFirstMostVisibleMonth( + state: CalendarState, + viewportPercent: Float = 50f, +): CalendarMonth { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleMonth) } + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.firstMostVisibleMonth(viewportPercent) } + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + + +@Composable +fun rememberFirstMostVisibleWeek( + state: WeekCalendarState, + viewportPercent: Float = 50f, +): Week { + val visibleMonth = remember(state) { mutableStateOf(state.firstVisibleWeek) } + LaunchedEffect(state) { + snapshotFlow { state.layoutInfo.firstMostVisibleWeek(viewportPercent) } + .filterNotNull() + .collect { month -> visibleMonth.value = month } + } + return visibleMonth.value +} + + +private fun CalendarLayoutInfo.firstMostVisibleMonth(viewportPercent: Float = 50f): CalendarMonth? { + return if (visibleMonthsInfo.isEmpty()) { + null + } else { + val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f + visibleMonthsInfo.firstOrNull { itemInfo -> + if (itemInfo.offset < 0) { + itemInfo.offset + itemInfo.size >= viewportSize + } else { + itemInfo.size - itemInfo.offset >= viewportSize + } + }?.month + } +} + + +private fun WeekCalendarLayoutInfo.firstMostVisibleWeek(viewportPercent: Float = 50f): Week? { + return if (visibleWeeksInfo.isEmpty()) { + null + } else { + val viewportSize = (viewportEndOffset + viewportStartOffset) * viewportPercent / 100f + visibleWeeksInfo.firstOrNull { itemInfo -> + if (itemInfo.offset < 0) { + itemInfo.offset + itemInfo.size >= viewportSize + } else { + itemInfo.size - itemInfo.offset >= viewportSize + } + }?.week + } +} + +fun CalendarDay.week(): Week{ + val weekList = mutableListOf() + val dayOrder = this.date.dayOfWeek.ordinal + var mondayLocalDate = this.date.minusDays(dayOrder.toLong()) + + DayOfWeek.values().forEach { + weekList.add(WeekDay(mondayLocalDate, WeekDayPosition.RangeDate)) + mondayLocalDate = mondayLocalDate.plusDays(1) + } + return Week(weekList) + +} + + +private class MutableStateAdapter( + private val state: State, + private val mutate: (T) -> Unit +) : MutableState { + + override var value: T + get() = state.value + set(value) { + mutate(value) + } + + override fun component1(): T = value + override fun component2(): (T) -> Unit = { value = it } +} + +// Flow +@Composable +fun MutableStateFlow.collectAsMutableState( + context: CoroutineContext = EmptyCoroutineContext +): MutableState = MutableStateAdapter( + state = collectAsState(context), + mutate = { value = it } +) + diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Components.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Components.kt new file mode 100644 index 0000000..1900091 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Components.kt @@ -0,0 +1,741 @@ +package ru.megboyzz.dnevnik.ui.components + +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.selection.toggleable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.* +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.* +import androidx.compose.ui.window.DialogProperties +import coil.compose.AsyncImage +import ru.megboyzz.dnevnik.* +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.theme.* +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.SpacerHeight +import ru.megboyzz.dnevnik.ui.SpacerWidth +import java.util.Date + + +//Рефакторинг компонентов: разбиение файла с компонентами на отдельные файлы +//Этот файл должен хранить только базовые реализации компонентов и общие компонеты компонеты + + + +@Composable +fun AlmostOutlinedText( + text: String, + contentColor: Color = white, + textAlign: TextAlign = TextAlign.Center +){ + Column() { + Divider(color = contentColor, thickness = 1.dp) + Column( + modifier = Modifier + .fillMaxWidth() + .height(30.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = text, + color = contentColor, + textAlign = textAlign, + style = H2, + modifier = Modifier + .fillMaxWidth() + ) + } + Divider(color = contentColor, thickness = 1.dp) + } +} + +@Composable +fun ProfileCard( + textColor: Color = white, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Start + ) { + // TODO добавить контент + AsyncImage( + modifier = Modifier + .padding(10.dp, 15.dp, 20.dp, 15.dp) + .clip(RoundedCornerShape(25.dp)) + .size(50.dp), + contentScale = ContentScale.Crop, + model = "", + contentDescription = "" + ) + Column(verticalArrangement = Arrangement.spacedBy(5.dp)) { + Text( + text = "Имя", + style = H1, + color = textColor + ) + Text( + text = "Класс", + style = H1, + color = textColor + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainTextField( + value: String, + onChange: (String) -> Unit, + isError: Boolean, + isLoading: Boolean = false, + onErrorChanged: (Boolean) -> Unit, + label: String = "", + keyboardType: KeyboardType = KeyboardType.Text, +) { + + var passwordVisible by rememberSaveable { mutableStateOf(false) } + val focusRequester = FocusRequester() + + OutlinedTextField( + value = value, + onValueChange = onChange, + label = { + Text( + text = label, + style = H2, + color = dark + ) + }, + shape = RoundedCornerShape(10.dp), + textStyle = H2, + colors = TextFieldDefaults.outlinedTextFieldColors( + //textColor = dark, + unfocusedBorderColor = if (isError) falseColor else mainBlue, + focusedBorderColor = if (isError) falseColor else mainBlue, + focusedLabelColor = dark, + unfocusedLabelColor = dark, + cursorColor = dark + ), + maxLines = 1, + keyboardOptions = KeyboardOptions( + keyboardType = keyboardType + ), + visualTransformation = + if (keyboardType == KeyboardType.Password) { + if (passwordVisible) + VisualTransformation.None + else + PasswordVisualTransformation() + } else VisualTransformation.None, + trailingIcon = { + if (keyboardType == KeyboardType.Password) { + val image = if (passwordVisible) + Icons.Filled.Visibility + else Icons.Filled.VisibilityOff + + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, "") + } + } else { + if(isLoading) LoginProgress() + } + }, + modifier = Modifier + .focusRequester(focusRequester) + .onFocusChanged { + if (it.isFocused) + if (isError) onErrorChanged(false) + } + .fillMaxWidth(), + ) +} + +@Composable +fun LoginProgress() { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + strokeWidth = 2.dp, + color = mainBlue + ) +} + +@Composable +fun MainCheckbox( + isChecked: Boolean, + modifier: Modifier = Modifier, + size: Float = 24f, + checkedColor: Color = mainBlue, + uncheckedColor: Color = lightGray, + checkmarkColor: Color = white, + onValueChange: (Boolean) -> Unit +) { + + val checkboxColor: Color by animateColorAsState( + if (isChecked) checkedColor else uncheckedColor, + label = "" + ) + val density = LocalDensity.current + val duration = 200 + + Row( + modifier = modifier + .toggleable( + value = isChecked, + role = Role.Checkbox, + onValueChange = onValueChange + ) + ) { + Card( + elevation = CardDefaults.cardElevation( + defaultElevation = 8.dp + ), + shape = RoundedCornerShape(8.dp), + border = BorderStroke(1.dp, color = checkedColor), + ) { + Box( + modifier = Modifier + .size(size.dp) + .background(checkboxColor), + contentAlignment = Alignment.Center + ) { + androidx.compose.animation.AnimatedVisibility( + visible = isChecked, + enter = slideInHorizontally( + animationSpec = tween(duration) + ) { + (size * -0.5).toInt() + } + expandHorizontally( + expandFrom = Alignment.Start, + animationSpec = tween(duration) + ), + exit = fadeOut() + ) { + Icon( + Icons.Default.Check, + contentDescription = null, + tint = checkmarkColor + ) + } + } + } + } +} + +@Composable +fun MainButton( + text: String, + radius: Dp = 8.dp, + onClick: () -> Unit +) { + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = mainBlue, + contentColor = Color.White + ), + shape = RoundedCornerShape(radius), + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = text, + style = H1 + ) + } +} + +//Базовая карточка для оценок, карточек с диаграммами +@Composable +fun BaseCard( + minWidth: Dp = 300.dp, + //Слой над карточкой + nextLayer: @Composable () -> Unit = {}, + content: @Composable () -> Unit +){ + val shape = RoundedCornerShape(10.dp) + //Расположение слоя регулируется этим алигментом + Box(contentAlignment = Alignment.TopEnd) { + Box( + Modifier + .clip(shape) + .background( + color = white, + shape = shape + ) + .defaultMinSize(minWidth) + .border( + border = BorderStroke( + width = 1.dp, + color = mainBlue, + ), + shape = shape + ) + ) { content() } + nextLayer() + } +} + +@Composable +fun AverageText( + title: String, + mark: String, +) { + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = title, + style = Mini, + color = white + ) + Text( + text = mark, + style = MainText, + color = white + ) + } +} + +@Composable +fun CardWithAverage( + averageMark: Float?, + resultMark: Float?, + minWidth: Dp = 300.dp, + content: @Composable () -> Unit +) { + BaseCard( + minWidth = minWidth, + nextLayer = { + Box( + modifier = Modifier + .size(100.dp, 50.dp) + .background(color = mainBlue, shape = RoundedCornerShape(10.dp)) + .clip(RoundedCornerShape(10.dp)), + ){ + Box( + modifier = Modifier.size(50.dp), + contentAlignment = Alignment.Center + ) { + AverageText( + title = R.string.title_average_mark.AsString(), + mark = if(averageMark != null) "%.2f".format(averageMark).replace(",", ".") else "-" + ) + } + } + Box( + modifier = Modifier + .size(50.dp) + .fillMaxSize() + .background(color = totalMarkColor, shape = RoundedCornerShape(10.dp)) + .clip(RoundedCornerShape(10.dp)), + contentAlignment = Alignment.Center + ){ + AverageText( + title = R.string.title_result_mark.AsString(), + mark = resultMark?.toString() ?: "-" + ) + } + } + ) { content() } +} + + +//TODO переместить это в слой данных +data class MarkModel( + val subjectName: String, + val mark: String, + val date: Date, + val teacher: String, + val reason: String = "null" +) + +@Composable +fun LastMarkCard( + markModel: MarkModel +) { + BaseCard { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.padding(15.dp) + ) { + Column( + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.width(200.dp) + ) { + Text( + text = markModel.subjectName, + style = MainText + ) + Text( + text = markModel.mark.toString(), + style = H1, + color = mainBlue + ) + } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + Text( + text = "${R.string.title_mark_recive.AsString()} ${markModel.date}", + style = MainText, + color = dark + ) + Text( + text = "${R.string.title_teacher_name.AsString()}: ${markModel.teacher}", + style = MainText, + color = dark + ) + if(markModel.reason != "null") + Text( + text = "${R.string.title_reason.AsString()} ${markModel.reason}", + style = MainText, + color = dark + ) + } + } + } +} + +@Composable +fun UnderlinedText( + text: String +) { + Column { + Column( + modifier = Modifier + .fillMaxWidth() + .height(30.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = text, + color = dark, + textAlign = TextAlign.Center, + style = H1, + modifier = Modifier + .fillMaxWidth() + ) + } + SpacerHeight(height = 2.dp) + Divider(color = dark, thickness = 1.dp) + } +} + +@Composable +fun AllMarksCard( + subjectName: String, + teacher: String?, + marks: List?, + resultMark: Float? +) { + CardWithAverage( + averageMark = marks?.average()?.toFloat(), + resultMark = resultMark + ) { + Box(Modifier.padding(15.dp, 15.dp)) { + Column { + Row( + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = R.drawable.ic_subject.AsPainter(), + contentDescription = "" + ) + Column( + verticalArrangement = Arrangement.Center + ) { + Text( + text = subjectName, + color = dark, + style = MainText, + overflow = TextOverflow.Ellipsis + ) + Text( + text = teacher ?: "-", + color = dark, + style = MainText, + overflow = TextOverflow.Ellipsis + ) + } + } + + Divider( + color = dark, + thickness = 1.dp, + modifier = Modifier.padding( + top = 15.dp, + bottom = 10.dp, + ) + ) + + if(marks != null) + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 20.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items(marks){ + MarkInBox(mark = it) + } + } + else { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth() + ){ + EmptyMessage(message = R.string.title_no_marks.AsString()) + } + } + } + } + } +} + +@Composable +fun MarkInBox(mark: Int) { + Box( + modifier = Modifier + .size(19.dp) + .fillMaxSize() + .background( + color = mainBlue, + shape = RoundedCornerShape(2.dp) + ), + contentAlignment = Alignment.Center + ){ + Text( + text = mark.toString(), + color = white + ) + } +} + +@Composable +fun ContentMessage( + message: String, + isFine: Boolean = true +) { + Column( + modifier = Modifier + .defaultMinSize(200.dp) + .padding(25.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = if(isFine) R.drawable.ic_smile.AsPainter() else R.drawable.ic_sad_smile.AsPainter(), + contentDescription = "" + ) + SpacerHeight(height = 10.dp) + Text( + text = message, + style = MainText, + color = dark + ) + } +} + + +@Composable +fun EmptyMessage(message: String) = ContentMessage(message = message, isFine = false) + +@Composable +fun FineMessage(message: String) = ContentMessage(message = message) + +@Composable +fun MessageAlert( + content: @Composable () -> Unit +) { + BaseCard { + Box( + modifier = Modifier.defaultMinSize(300.dp), + contentAlignment = Alignment.Center, + ){ + content.invoke() + } + } +} + +@Composable +fun MessageAlertInWork() { + MessageAlert { + EmptyMessage(message = "Раздел находится в разработке!") + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BaseAlertBox( + title: String?, + onDismissRequest: () -> Unit, + content: @Composable () -> Unit, + buttons: @Composable () -> Unit +) { + //TODO переверстать + AlertDialog( + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = true + ), + //shape = RoundedCornerShape(20.dp), + onDismissRequest = onDismissRequest, + ){ + if(title != null) + Text( + text = title, + style = H1 + ) + content() + buttons() + } +} + +@Composable +fun AlertMessageBox( + title: String, + alertText: String, + onClick: () -> Unit, + onCancel: () -> Unit = {}, +) { + + BaseAlertBox( + title = title, + onDismissRequest = onCancel, + content = { + Text( + text = alertText, + style = H2, + color = dark + ) + }, + buttons = { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + Button( + onClick = onClick, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = mainBlue, + contentColor = white + ), + modifier = Modifier + .defaultMinSize(170.dp) + .padding(bottom = 30.dp) + ) { + Text( + text = R.string.title_ok.AsString(), + style = H1 + ) + } + } + } + ) + +} + + +@Composable +fun LeaveAlert( + isLoading: Boolean = false, + onAgree: () -> Unit, + onCancel: () -> Unit, +){ + BaseAlertBox( + title = R.string.tile_leave.AsString(), + onDismissRequest = onCancel, + content = { + Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + Text( + text = R.string.text_leave.AsString(), + style = H2, + color = dark + ) + if(isLoading) CircularProgressIndicator() + } + }, + buttons = { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + AlertButton( + title = R.string.title_cancel.AsString(), + onClick = onCancel + ) + SpacerWidth(width = 10.dp) + AlertButton( + title = R.string.title_button_leave.AsString(), + onClick = onAgree + ) + + } + } + ) +} + +@Composable +fun AlertButton( + title: String, + onClick: () -> Unit +) { + Button( + onClick = onClick, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = mainBlue, + contentColor = white + ), + modifier = Modifier + .padding(bottom = 30.dp) + ){ + Text( + text = title, + style = H1 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/HomeWorkCard.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/HomeWorkCard.kt new file mode 100644 index 0000000..5a997fe --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/HomeWorkCard.kt @@ -0,0 +1,156 @@ +package ru.megboyzz.dnevnik.screens.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.components.BaseCard +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.dark +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import java.time.LocalDate +import java.time.format.DateTimeFormatter +import java.util.* + + +@Composable +fun BaseHomeWorkCard( + subjectName: String, + homeWorkTaskText: String, + deadlineDate: LocalDate, + deadlineLesson: Int = -1, + isDone: Boolean, + onClickBox: (Boolean) -> Unit +) { + val deadlineLessonText = if(deadlineLesson != -1) "$deadlineLesson-го урока" else "" + + val format = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.getDefault()) + var expanded by remember { + mutableStateOf(false) + } + + BaseCard { + Box( + modifier = Modifier + .defaultMinSize(300.dp) + .padding(5.dp) + ){ + Column { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.defaultMinSize(500.dp) + ){ + HomeWorkIsDoneCheckBox(isDone = isDone, onClickBox = onClickBox) + Text( + text = subjectName, + style = H2, + color = dark, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Center + ) + HomeWorkCardToggler(expanded = expanded, onChangeExpand = {expanded = !it}) + } + if(expanded) { + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + modifier = Modifier.padding(5.dp) + ) { + HomeworkText(text = homeWorkTaskText) + Text( + text = "${R.string.title_deadline.AsString()} ${ + deadlineDate.format( + format + ) + } $deadlineLessonText", + style = H2, + color = dark + ) + } + } + } + } + } +} + +@Composable +fun HomeWorkIsDoneCheckBox( + isDone: Boolean, + onClickBox: (Boolean) -> Unit +) { + val shape = RoundedCornerShape(9.dp) + Box( + modifier = Modifier + .size(25.dp, 20.dp) + .clip(shape) + .border( + border = BorderStroke( + width = 1.dp, + color = mainBlue + ), + shape = shape + ) + .clickable { onClickBox.invoke(isDone) }, + contentAlignment = Alignment.Center + ){ + if(isDone) + Image( + painter = R.drawable.ic_done.AsPainter(), + contentDescription = "" + ) + } +} + +@Composable +fun HomeWorkCardToggler( + expanded: Boolean, + onChangeExpand: (Boolean) -> Unit +) { + Box(modifier = Modifier.size(24.dp)) { + IconButton( + onClick = { onChangeExpand.invoke(expanded) }, + modifier = Modifier.fillMaxSize() + ) { + Icon( + painter = (if (expanded) R.drawable.ic_hw_toggled else R.drawable.ic_hw_untoggled).AsPainter(), + contentDescription = "" + ) + } + } +} + + +@Composable +fun HomeworkText( + text: String, +){ + Column() { + Divider(color = dark, thickness = 1.dp, modifier = Modifier.width(500.dp)) + Column( + modifier = Modifier + .defaultMinSize(500.dp) + .height(40.dp), + verticalArrangement = Arrangement.Center + ) { + Text( + text = text, + color = dark, + style = H2, + ) + } + Divider(color = dark, thickness = 1.dp, modifier = Modifier.width(500.dp)) + } +} diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/LoginScreen.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/LoginScreen.kt new file mode 100644 index 0000000..ae450e0 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/LoginScreen.kt @@ -0,0 +1,177 @@ +package ru.megboyzz.dnevnik.screens.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import ru.megboyzz.dnevnik.* +import ru.megboyzz.dnevnik.ui.theme.Big +import ru.megboyzz.dnevnik.ui.theme.MainText +import ru.megboyzz.dnevnik.ui.theme.Mini +import ru.megboyzz.dnevnik.ui.theme.dark +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.components.MainButton +import ru.megboyzz.dnevnik.ui.components.MainCheckbox +import ru.megboyzz.dnevnik.ui.components.MainTextField +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.SpacerHeight +import ru.megboyzz.dnevnik.ui.SpacerWidth + +@Composable +fun LoginScreenContent( + login: String, + onLoginChange: (String) -> Unit, + password: String, + onPasswordChange: (String) -> Unit, + isLoading: Boolean, + isError: Boolean, + onErrorChange: (Boolean) -> Unit, + isRememberMe: Boolean, + onRememberChange: (Boolean) -> Unit, + onSignIn: () -> Unit, + onSignUp: () -> Unit, + onForgetPassword: () -> Unit +) { + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp + + var defaultPadding = 65.dp + + val isNormal = screenWidth < 500 + + Box(Modifier.fillMaxSize()) { + + if(!isNormal) BigLoginBackground() + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + if (isNormal) LoginBackground() + else { + defaultPadding *= 4 + } + Row(horizontalArrangement = Arrangement.Center) { + Text( + text = R.string.title_welcome.AsString(), + style = Big, + textAlign = TextAlign.Center, + modifier = Modifier.padding(defaultPadding, 20.dp, defaultPadding, 40.dp), + color = dark + ) + } + Column( + modifier = Modifier.padding(defaultPadding, 0.dp) + ) { + Column(Modifier.fillMaxWidth()) { + MainTextField( + value = login, + label = R.string.title_login.AsString(), + isError = isError, + isLoading = isLoading, + onErrorChanged = onErrorChange, + onChange = onLoginChange + ) + SpacerHeight(15.dp) + MainTextField( + value = password, + keyboardType = KeyboardType.Password, + label = R.string.title_password.AsString(), + isError = isError, + onErrorChanged = onErrorChange, + onChange = onPasswordChange + ) + } + SpacerHeight(5.dp) + Row( + modifier = Modifier + .fillMaxWidth() + .height(30.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Row( + modifier = Modifier.fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically + ) { + MainCheckbox( + isChecked = isRememberMe, + onValueChange = onRememberChange + ) + SpacerWidth(5.dp) + Text( + text = R.string.title_remember_me.AsString(), + style = MainText, + color = dark + ) + } + Text( + text = R.string.title_forget_password.AsString(), + style = MainText, + color = dark, + modifier = Modifier.clickable(onClick = onForgetPassword) + ) + } + Column( + modifier = Modifier + .fillMaxSize() + .padding(0.dp, 10.dp), + verticalArrangement = Arrangement.Center + ) { + MainButton( + text = R.string.title_sign_in.AsString(), + onClick = onSignIn + ) + SpacerHeight(5.dp) + Text( + text = R.string.title_sign_up.AsString(), + modifier = Modifier + .clickable(onClick = onSignUp) + .padding(5.dp) + .fillMaxWidth(), + textAlign = TextAlign.Center, + style = Mini, + color = dark + ) + } + } + } + } +} + +@Composable +fun LoginBackground() { + val icon = R.drawable.ic_header.AsPainter() + val size = icon.intrinsicSize + Column { + Image( + painter = icon, + contentDescription = "", + modifier = Modifier.fillMaxWidth(), + contentScale = ContentScale.FillWidth, + alignment = Alignment.TopStart + ) + } +} + +@Composable +fun BigLoginBackground() { + val icon = R.drawable.header_big.AsPainter() + Column(Modifier.fillMaxWidth()) { + Image( + painter = icon, + contentDescription = "", + modifier = Modifier.fillMaxWidth(), + alignment = Alignment.TopStart + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Previews.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Previews.kt new file mode 100644 index 0000000..c423c0f --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/Previews.kt @@ -0,0 +1,409 @@ +package ru.megboyzz.dnevnik.screens.ui + +import android.util.Log +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController +import com.kizitonwose.calendar.core.daysOfWeek +import ru.megboyzz.dnevnik.ui.navigate +import ru.megboyzz.dnevnik.navigation.AppNavRoute +import ru.megboyzz.dnevnik.screens.SettingsScreen +import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel +import ru.megboyzz.dnevnik.screens.ui.* +import ru.megboyzz.dnevnik.ui.components.calendar.NiceCalendar +import ru.megboyzz.dnevnik.screens.ui.main.DrawerContent +import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold +import ru.megboyzz.dnevnik.screens.ui.piechart.PieChart +import ru.megboyzz.dnevnik.screens.ui.piechart.PieChartCard +import ru.megboyzz.dnevnik.screens.ui.piechart.PieChartData +import ru.megboyzz.dnevnik.screens.ui.schedule.BaseScheduleCallsCard +import ru.megboyzz.dnevnik.screens.ui.schedule.BaseScheduleCard +import ru.megboyzz.dnevnik.screens.ui.schedule.BaseScheduleShield +import ru.megboyzz.dnevnik.screens.ui.schedule.CallData +import ru.megboyzz.dnevnik.ui.components.AlertMessageBox +import ru.megboyzz.dnevnik.ui.components.AllMarksCard +import ru.megboyzz.dnevnik.ui.components.AlmostOutlinedText +import ru.megboyzz.dnevnik.ui.components.CardWithAverage +import ru.megboyzz.dnevnik.ui.components.ContentMessage +import ru.megboyzz.dnevnik.ui.components.EmptyMessage +import ru.megboyzz.dnevnik.ui.components.FineMessage +import ru.megboyzz.dnevnik.ui.components.LastMarkCard +import ru.megboyzz.dnevnik.ui.components.MessageAlert +import ru.megboyzz.dnevnik.ui.components.UnderlinedText +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.white +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.TextStyle +import java.util.* + +//Файл со всеми превьюшками + +@Composable +fun NicePreviewContainer( + content: @Composable () -> Unit +) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + .height(1500.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { content() } +} + +@Preview +@Composable +fun text() { + Row(Modifier.fillMaxWidth()) { + AlmostOutlinedText(text = "Здарова карова") + } +} + +@OptIn(ExperimentalAnimationApi::class) +@Preview +@Composable +fun DrawerPrev() { + val scaffoldState = rememberScaffoldState() + val navController = rememberNavController() + Box(Modifier.background(mainBlue)) { + DrawerContent(navController = navController, scaffoldState = scaffoldState) + } + +} + + +@Preview +@Composable +fun CardWithAveragePrev() { + CardWithAverage( + averageMark = 4.33f, + resultMark = 4f + ) { + Box(Modifier.padding(10.dp)){ + Text("hehe") + } + } +} + +@Preview +@Composable +fun BaseCardPrev() { + Box( + Modifier + .fillMaxSize() + ){ + Column( + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + + Card( + backgroundColor = white, + shape = RoundedCornerShape(10.dp), + border = BorderStroke( + width = 1.dp, + color = mainBlue + ), + modifier = Modifier.defaultMinSize(minWidth = 300.dp) + ) { + Box( + Modifier + .size(50.dp) + .background(Color.Red)){} + } + } + } +} + +@Preview +@Composable +fun UnderlinedTextPrev() { + Box(Modifier.background(white)){ + UnderlinedText(text = "Общая сводка") + } +} + +@Preview +@Composable +fun CirclePrev() { + val list = listOf( + PieChartData(legend = "История", value=5), + PieChartData(legend = "Русский язык", value=4), + PieChartData(legend = "Химия", value=1), + PieChartData(legend = "Математика", value=3) + ) + PieChart(list) +} + +@Preview +@Composable +fun GoodPieChartPrev() { + val list = listOf( + PieChartData(legend = "История", value=5), + PieChartData(legend = "Русский язык", value=4), + PieChartData(legend = "Химия", value=1), + PieChartData(legend = "Математика", value=3), + ) + PieChart(diagramData = list) +} + +@Preview +@Composable +fun PieChartCardPrev() { + val list = listOf( + PieChartData(legend = "История", value=5), + PieChartData(legend = "Русский язык", value=4), + PieChartData(legend = "Химия", value=1), + PieChartData(legend = "Математика", value=3), + ) + PieChartCard( + title = "Сводка по оценкам отлично", + data = list + ) +} + + +@Preview +@Composable +fun AllMarksCardPrev1() { + NicePreviewContainer { + AllMarksCard( + subjectName = "Математика", + teacher = "Степанова С.А.", + marks = listOf(4, 5, 5, 5, 5, 5, 4, 5, 3, 2), + resultMark = 4f + ) + AllMarksCard( + subjectName = "Русский язык", + teacher = "Степанова С.А.", + marks = listOf(4, 5, 5, 5, 5, 5, 4, 5, 3, 2), + resultMark = null + ) + AllMarksCard( + subjectName = "Русский язык", + teacher = "Степанова С.А.", + marks = null, + resultMark = 4.2f + ) + AllMarksCard( + subjectName = "Русский язык", + teacher = null, + marks = listOf(4, 5, 5, 5, 5, 5, 4, 5, 3, 2), + resultMark = 4.2f + ) + + AllMarksCard( + subjectName = "Математика", + teacher = "Степанова С.А.", + marks = null, + resultMark = null + ) + + AllMarksCard( + subjectName = "Математика", + teacher = null, + marks = null, + resultMark = null + ) + } +} + +@Preview +@Composable +fun NiceCalendarPrev() { + + var startMonth by remember{ + mutableStateOf(YearMonth.now()) + } + + NiceCalendar( + currentMonth = startMonth, + ){ day, month, year -> + + } +} + +@Preview +@Composable +fun EmptyPrev() { + ContentMessage(message = "Таких оценок нет") +} + +@Preview +@Composable +fun PieChartNullPrev() { + val list = listOf( + PieChartData(legend = "История", value=5), + PieChartData(legend = "Русский язык", value=4), + PieChartData(legend = "Химия", value=1), + PieChartData(legend = "Математика", value=3), + ) + NicePreviewContainer { + PieChartCard( + title = "Сводка по оценкам отлично", + data = list + ) + PieChartCard( + title = "Сводка по оценкам хорошо", + data = null + ) + } +} + + +@Preview +@Composable +fun MessagesPrev() { + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + MessageAlert { + EmptyMessage(message = "Таких оценок нет((") + } + MessageAlert { + FineMessage(message = "Сегодня нет уроков!") + } + } + +} + +@Preview +@Composable +fun HomeWorkCardPrev() { + var isDone by remember { + mutableStateOf(false) + } + BaseHomeWorkCard( + subjectName = "Алгебра и начала математического анализа", + homeWorkTaskText = "C. 45 №45, №56, №46", + deadlineDate = LocalDate.now(), + isDone = isDone, + onClickBox = { + isDone = !it + } + ) +} + + +@Preview +@Composable +fun ScheduleShieldPrev() { + BaseScheduleShield( + subjectName = "Физика", + teacherName = "Иванов Иван Иванович", + classNumber = "3.13", + nextLessonName = "Обществознание" + ) +} + +@Preview +@Composable +fun ScheduleCardPrev() { + + val daysOfWeek = daysOfWeek().dropLast(1) + Log.i("Locale", Locale.getDefault().country) + LazyVerticalGrid( + columns = GridCells.Fixed(2), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ){ + items(daysOfWeek){ + BaseScheduleCard( + dayTitle = it.getDisplayName(TextStyle.FULL, Locale.getDefault()), + subjectNames = listOf( + "Литература", + "Алгебра", + "Геометрия", + "Алгебра и начала математического анализа", + "Физика", + "Астрономия", + ) + ) + } + } +} + +@Preview +@Composable +fun ScheduleCallsPrev() { + val calls = listOf( + CallData("9:00", "10:30"), + CallData("10:40", "12:10"), + CallData("12:40", "14:10"), + CallData("14:20", "15:50"), + CallData("18:00", "19:30") + ) + + BaseScheduleCallsCard(callsList = calls) + +} + +@Preview +@Composable +fun ScaffoldTest() { + val scaffoldState = rememberScaffoldState() + val navController = rememberNavController() + SettingsScaffold( + onLeave = { navController.navigate(AppNavRoute.Marks) }, + scaffoldState = scaffoldState, + navController = navController + ) { + + } +} + +@Preview +@Composable +fun SettingsTest() { + + SettingsScreen(navController = rememberNavController(), scaffoldState = rememberScaffoldState()) + +} + +@Preview +@Composable +fun Test1() { + + AlertMessageBox( + title = "Информация", + alertText = "В данный момент на сервере ведутся технические работы, вход невозможен, повторите попытку позднее", + onClick = { /*TODO*/ } + ) + +} + +@Preview +@Composable +fun AlertTest() { + + LastMarkCard(markModel = MarkModel( + subjectName = "Обществознание", + mark = 4.toString(), + teacher = "Расита", + reason = "Ответ на уроке", + date = Date("2023-04-13T00:00:00") + )) + +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/SubScreenNavigation.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/SubScreenNavigation.kt new file mode 100644 index 0000000..8e96789 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/SubScreenNavigation.kt @@ -0,0 +1,90 @@ +package ru.megboyzz.dnevnik.screens.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import ru.megboyzz.dnevnik.ui.navigate +import ru.megboyzz.dnevnik.navigation.BaseNavRote +import ru.megboyzz.dnevnik.ui.theme.MainText +import ru.megboyzz.dnevnik.ui.theme.dark +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.white + +@Composable +fun SubScreenNavButton( + text: String, + isClicked: MutableState, + onClick: () -> Unit +) { + val colors = ButtonDefaults.buttonColors( + backgroundColor = if(isClicked.value) mainBlue else white, + contentColor = if(isClicked.value) white else dark + ) + Button( + onClick = { + isClicked.value = !isClicked.value + onClick.invoke() + }, + shape = RoundedCornerShape(20.dp), + border = BorderStroke( + width = 1.dp, + color = mainBlue, + ), + colors = colors, + ) { + Text( + text = text, + style= MainText + ) + } +} + +data class SubScreenData( + val title: String, + val to: BaseNavRote, + val isClicked: MutableState +) + + +@Composable +fun SubScreenNavBar( + list: List, + subScreenNavController: NavController +) { + val horizontalScroll = rememberScrollState() + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(horizontalScroll) + .padding(15.dp), + horizontalArrangement = Arrangement.spacedBy(15.dp) + ){ + for(data in list){ + if(data.isClicked.value) + subScreenNavController.navigate(data.to) + SubScreenNavButton( + text = data.title, + isClicked = data.isClicked + ) { + for(d in list){ + if(data != d) { + d.isClicked.value = false + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/Calendar.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/Calendar.kt new file mode 100644 index 0000000..ce0796a --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/Calendar.kt @@ -0,0 +1,351 @@ +package ru.megboyzz.dnevnik.ui.components.calendar + +import android.util.Log +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.kizitonwose.calendar.compose.CalendarState +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.WeekCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState +import com.kizitonwose.calendar.core.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.theme.* +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.rememberFirstMostVisibleMonth +import ru.megboyzz.dnevnik.ui.week +import java.time.DayOfWeek +import java.time.LocalDate +import java.time.Month +import java.time.YearMonth +import java.time.format.TextStyle +import java.util.* + +//TODO доделать свертку календаря, он почти идеально работает +@Composable +fun NiceCalendar( + currentMonth: YearMonth, + onClick: (day: CalendarDay, month: YearMonth, year: Int) -> Unit +) { + + val selections = remember { mutableStateListOf() } + val daysOfWeek = remember { daysOfWeek() } + var transitionEnabled by remember { mutableStateOf(true) } + + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = daysOfWeek.first() + ) + + val weekState = rememberWeekCalendarState( + startDate = currentMonth.atDay(1), + endDate = currentMonth.atDay(7), + firstVisibleWeekDate = currentMonth.atDay(1), + firstDayOfWeek = daysOfWeek.first() + ) + + var selectedWeek by remember { mutableStateOf(null) } + + val monthScrollCoroutineScope = rememberCoroutineScope() + val weekScrollCoroutineScope = rememberCoroutineScope() + + val visibleMonth = rememberFirstMostVisibleMonth(state, viewportPercent = 90f) + + Column { + + SimpleCalendarTitle( + currentMonth = visibleMonth.yearMonth, + goToPrevious = { + state.scrollToPreviousMonth(monthScrollCoroutineScope) + }, + goToNext = { + state.scrollToNextMonth(monthScrollCoroutineScope) + }, + transitionEnabled = transitionEnabled + ) + + Card( + shape = RoundedCornerShape(10.dp), + modifier = Modifier.defaultMinSize(300.dp) + ) { + if(selectedWeek == null) { + transitionEnabled = true + HorizontalCalendar( + state = state, + dayContent = { day -> + + MonthDay( + day = day, + isSelected = selections.contains(day.date) + ) { clickedDay -> + Log.i("Click!", "MonthDay is Clicked!") + //Чтобы два раза не вызывать week() + val week = clickedDay.week() + + if(!selections.contains(day.date)) { + selections.removeIf { it != day.date } + selections.add(day.date) + }else selections.remove(day.date) + + weekScrollCoroutineScope.launch { + weekState.startDate = week.days.first().date + weekState.endDate = week.days.last().date + weekState.animateScrollToWeek(clickedDay.date) + } + + selectedWeek = if (selectedWeek == week) null else week + onClick(clickedDay, currentMonth, currentMonth.year) + } + }, + monthHeader = { DaysOfWeekTitle(daysOfWeek = DayOfWeek.values().asList()) }, + modifier = Modifier.padding(10.dp) + ) + } + else { + transitionEnabled = false + WeekCalendar( + state = weekState, + weekHeader = { _ -> + DaysOfWeekTitle(daysOfWeek = DayOfWeek.values().asList()) + }, + dayContent = { day -> + WeekDay( + day = day, + isSelected = selections.contains(day.date) + ) { calendarDay -> + Log.i("Click!", "WeekDay is Clicked!") + if(!selections.contains(day.date)) { + selections.removeIf { it != day.date } + selections.add(day.date) + }else selections.remove(day.date) + selectedWeek = null + + } + }, + modifier = Modifier.padding(10.dp) + ) + } + } + } +} + + +fun CalendarState.scrollToPreviousMonth(coroutineScope: CoroutineScope){ + val it = this + coroutineScope.launch { + it.scrollToMonth(it.firstVisibleMonth.yearMonth.previousMonth) + } +} + +fun CalendarState.scrollToNextMonth(coroutineScope: CoroutineScope){ + val it = this + coroutineScope.launch { + it.scrollToMonth(it.firstVisibleMonth.yearMonth.nextMonth) + } +} + + +@Composable +fun CalendarNavigationIcon( + icon: Painter, + contentDescription: String, + onClick: () -> Unit, + enabled: Boolean = true +) = Box( + modifier = Modifier + .size(35.dp) + .aspectRatio(1f) + .clip(shape = CircleShape) + .clickable(role = Role.Button, onClick = onClick, enabled = enabled), +) { + Icon( + modifier = Modifier + .fillMaxSize() + .padding(4.dp) + .align(Alignment.Center), + painter = icon, + contentDescription = contentDescription, + ) +} + + +@Composable +fun SimpleCalendarTitle( + modifier: Modifier = Modifier, + currentMonth: YearMonth, + goToPrevious: () -> Unit, + goToNext: () -> Unit, + transitionEnabled: Boolean = true +) { + + val listOfMonth = listOf( + R.string.title_january.AsString(), + R.string.title_february.AsString(), + R.string.title_march.AsString(), + R.string.title_april.AsString(), + R.string.title_may.AsString(), + R.string.title_june.AsString(), + R.string.title_july.AsString(), + R.string.title_august.AsString(), + R.string.title_september.AsString(), + R.string.title_october.AsString(), + R.string.title_november.AsString(), + R.string.title_december.AsString() + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + contentAlignment = Alignment.Center, + ) { + Row( + modifier = modifier + .height(40.dp) + .padding(vertical = 0.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + CalendarNavigationIcon( + icon = R.drawable.ic_month_swipe_left.AsPainter(), + contentDescription = "Previous", + onClick = goToPrevious, + enabled = transitionEnabled + ) + Text( + text = currentMonth.displayText(listOfMonth), + style = H1, + color = dark + ) + CalendarNavigationIcon( + icon = R.drawable.ic_month_swipe_right.AsPainter(), + contentDescription = "Next", + onClick = goToNext, + enabled = transitionEnabled + ) + } + } +} + + + +fun YearMonth.displayText(list: List): String { + return "${list[this.month.ordinal]} ${this.year}" +} + +fun Month.displayText(short: Boolean = true): String { + val style = if (short) TextStyle.SHORT else TextStyle.FULL_STANDALONE + return getDisplayName(style, Locale.getDefault()) +} + +fun DayOfWeek.displayText(uppercase: Boolean = false): String { + return getDisplayName(TextStyle.SHORT, Locale.getDefault()).let { value -> + if (uppercase) value.uppercase(Locale.getDefault()) else value + } +} + +@Composable +fun DaysOfWeekTitle(daysOfWeek: List) { + Column { + Row(modifier = Modifier.fillMaxWidth()) { + for (dayOfWeek in daysOfWeek) { + Text( + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.getDefault()).uppercase(), + style = MainText + ) + } + } + Divider( + thickness = 1.dp, + color = dark, + modifier = Modifier.padding(vertical = 10.dp) + ) + } +} + +@Composable +fun MonthDay( + day: CalendarDay, + isSelected: Boolean, + onClick: (CalendarDay) -> Unit +) { + Day(day = day, isSelected = isSelected, onClick = onClick) +} + +@Composable +fun WeekDay( + day: WeekDay, + isSelected: Boolean, + onClick: (CalendarDay) -> Unit +) { + Log.i("WeekDay", day.toString()) + val position = when(day.position){ + WeekDayPosition.InDate -> DayPosition.MonthDate + WeekDayPosition.OutDate -> DayPosition.MonthDate + WeekDayPosition.RangeDate -> DayPosition.MonthDate + } + val newDay = CalendarDay(day.date, position) + Day(day = newDay, isSelected = isSelected, onClick = onClick) +} + +@Composable +fun Day( + day: CalendarDay, + isSelected: Boolean, + onClick: (CalendarDay) -> Unit +) { + Log.i("BaseDay", day.toString()) + Box( + modifier = Modifier + .aspectRatio(ratio = 1f) // This is important for square sizing! + .clickable( + enabled = day.position == DayPosition.MonthDate, + onClick = { onClick(day) } + ), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .size(30.dp) + .clip(CircleShape) + .background(color = if (isSelected) lightGray else Color.Transparent) + .border( + border = BorderStroke( + width = 1.dp, + color = if (isSelected) mainBlue else Color.Transparent + ), + shape = CircleShape + ), + contentAlignment = Alignment.Center + ) { + Text( + text = day.date.dayOfMonth.toString(), + style = MainText, + textAlign = TextAlign.Justify, + color = if (day.position == DayPosition.MonthDate) dark else Color.Gray, + ) + } + } +} diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/CalendarConstants.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/CalendarConstants.kt new file mode 100644 index 0000000..39fb1cd --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/calendar/CalendarConstants.kt @@ -0,0 +1,8 @@ +package ru.megboyzz.dnevnik.ui.components.calendar + +import java.time.YearMonth + +//Константы для календаря + +val startMonth: YearMonth = YearMonth.of(2009, 9) +val endMonth: YearMonth = YearMonth.of(2100, 9) \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/BottomBar.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/BottomBar.kt new file mode 100644 index 0000000..d23362b --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/BottomBar.kt @@ -0,0 +1,102 @@ +package ru.megboyzz.dnevnik.screens.ui.main + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import ru.megboyzz.dnevnik.* +import ru.megboyzz.dnevnik.navigation.AppNavRoute +import ru.megboyzz.dnevnik.ui.theme.MainText +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.white +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.drawColoredShadow +import ru.megboyzz.dnevnik.ui.mainClickable +import ru.megboyzz.dnevnik.util.* + +@Composable +fun BottomBar(navController: NavController) { + + Box( + modifier = Modifier + .fillMaxWidth() + .drawColoredShadow( + offsetY = 1.dp + ) + .height(90.dp) + .background(white) + .padding(10.dp, 10.dp, 10.dp, 15.dp), + ){ + Box( + Modifier + .fillMaxSize() + .clip(RoundedCornerShape(20.dp)) + .background(mainBlue) + ){ + Row( + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxSize() + ) { + BottomNavButton( + icon = R.drawable.ic_marks.AsPainter(), + text = R.string.title_marks.AsString(), + onClick = { navController.navigate(AppNavRoute.Marks) } + ) + BottomNavButton( + icon = R.drawable.ic_homework.AsPainter(), + text = R.string.title_hw.AsString(), + onClick = { navController.navigate(AppNavRoute.HomeWorks) } + ) + BottomNavButton( + icon = R.drawable.ic_schedule.AsPainter(), + text = R.string.title_schedule.AsString(), + onClick = { navController.navigate(AppNavRoute.Schedule) } + ) + } + } + } +} + +@Composable +fun BottomNavButton( + icon: Painter, + text: String, + color: Color = white, + onClick: () -> Unit, +) { + //Возможно стоит убрать анимацию клика + Box( + Modifier + .mainClickable(onClick = onClick, radius = 20.dp) + .padding(10.dp)) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + painter = icon, + contentDescription = "", + modifier = Modifier.padding(5.dp) + ) + Text( + text = text, + style = MainText, + color = color, + overflow = TextOverflow.Ellipsis + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Drawer.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Drawer.kt new file mode 100644 index 0000000..eec4253 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Drawer.kt @@ -0,0 +1,231 @@ +package ru.megboyzz.dnevnik.screens.ui.main + +import android.util.Log +import androidx.activity.viewModels +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import androidx.navigation.NavController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import ru.megboyzz.dnevnik.* +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.navigation.AppNavRoute +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.SpacerHeight +import ru.megboyzz.dnevnik.ui.SpacerWidth +import ru.megboyzz.dnevnik.ui.components.AlmostOutlinedText +import ru.megboyzz.dnevnik.ui.components.LeaveAlert +import ru.megboyzz.dnevnik.ui.components.ProfileCard +import ru.megboyzz.dnevnik.ui.mainClickable +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.white +import ru.megboyzz.dnevnik.util.* +import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel +import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel +import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory + + +@Composable +fun NiceContainerForDrawer( + content: @Composable () -> Unit +) { + + val elevation = 16.dp + + val shape = RoundedCornerShape( + topEnd = 20.dp, + bottomEnd = 20.dp + ) + + Box( + modifier = Modifier + .shadow(elevation) + .zIndex(1f) + .clip(shape) + ) { + Box( + modifier = Modifier + .background( + color = mainBlue, + shape = shape + ) + .clip(shape) + ) { + content.invoke() + } + } +} + +@Composable +fun DrawerContent( + navController: NavController, + scaffoldState: ScaffoldState +) { + + Log.i("DrawerContent", "Called") + + val activity = LocalContext.current as MainActivity + + val profileViewModel: ProfileViewModel by activity.viewModels{ + ViewModelFactory{ + activity.appComponent.profileViewModel + } + } + + val authorizationViewModel: AuthorizationViewModel by activity.viewModels { + ViewModelFactory{ + activity.appComponent.authViewModel + } + } + + val profile = profileViewModel.profile.collectAsState() + + val credentialsDao = activity.appComponent.credentialsDao + val globalUserContextDao = activity.appComponent.globalUserContextDao + + + + var leaveAlertIsOpened by remember { + mutableStateOf(false) + } + + var isLeavingProcess by remember { + mutableStateOf(false) + } + + val scope = rememberCoroutineScope() + + if(leaveAlertIsOpened){ + LeaveAlert( + isLoading = isLeavingProcess, + onAgree = { + scope.launch(Dispatchers.IO) { + isLeavingProcess = true + credentialsDao.deleteCredentials() + globalUserContextDao.deleteGlobalUserContext() + leaveAlertIsOpened = false + } + authorizationViewModel.resetAuth() + navController.navigate(AppNavRoute.Login) + }, + onCancel = { leaveAlertIsOpened = false } + ) + } + + NiceContainerForDrawer { + Column( + modifier = Modifier + .width(260.dp) + .fillMaxSize() + .padding(10.dp) + .background(mainBlue), + horizontalAlignment = Alignment.Start, + ) { + if(profile.value == null) + CircularProgressIndicator() + else + ProfileCard(userProfileModel = profile.value!!) + SpacerHeight(15.dp) + if(profile.value == null) + CircularProgressIndicator() + else + AlmostOutlinedText( + text = if(profile.value!!.isTerm) + String.format(R.string.title_quatter_format.AsString(), profile.value!!.term) + else + String.format(R.string.title_sem_format.AsString(), profile.value!!.term) + ) + SpacerHeight(20.dp) + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + ) { + DrawerMainButton( + icon = R.drawable.ic_marks.AsPainter(), + text = R.string.title_marks.AsString(), + onClick = { + navController.navigate(AppNavRoute.Marks) + scope.launch { scaffoldState.drawerState.close() } + } + ) + DrawerMainButton( + icon = R.drawable.ic_homework.AsPainter(), + text = R.string.title_hw.AsString(), + onClick = { + navController.navigate(AppNavRoute.HomeWorks) + scope.launch { scaffoldState.drawerState.close() } + } + ) + DrawerMainButton( + icon = R.drawable.ic_schedule.AsPainter(), + text = R.string.title_schedule.AsString(), + onClick = { + navController.navigate(AppNavRoute.Schedule) + scope.launch { scaffoldState.drawerState.close() } + } + ) + DrawerMainButton( + icon = R.drawable.ic_settings.AsPainter(), + text = R.string.title_settings.AsString(), + onClick = { + navController.navigate(AppNavRoute.Settings) + scope.launch { scaffoldState.drawerState.close() } + } + ) + } + Spacer(Modifier.weight(1f)) + Divider(color = white, thickness = 1.dp) + SpacerHeight(5.dp) + Column(Modifier.padding(5.dp, 0.dp)) { + DrawerMainButton( + icon = R.drawable.ic_exit.AsPainter(), + text = R.string.title_leave_from_acconut.AsString() + ) { + leaveAlertIsOpened = true + } + } + } + } +} + +@Composable +fun DrawerMainButton( + icon: Painter, + text: String, + textColor: Color = white, + onClick: () -> Unit = {} +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .height(30.dp) + .mainClickable { onClick.invoke() } + ){ + Image( + painter = icon, + contentDescription = "icon", + Modifier.padding(start = 10.dp) + ) + SpacerWidth(13.dp) + Text( + text = text, + style = H2, + color = textColor + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Scaffolds.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Scaffolds.kt new file mode 100644 index 0000000..31d2ddf --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/main/Scaffolds.kt @@ -0,0 +1,126 @@ +package ru.megboyzz.dnevnik.screens.ui.main + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Menu +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import kotlinx.coroutines.launch +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.SpacerWidth +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.white +import ru.megboyzz.dnevnik.R + +@Composable +fun MainScaffold( + icon: Painter, + title: String, + navController: NavController, + scaffoldState: ScaffoldState, + content: @Composable (PaddingValues) -> Unit, +) { + val scope = rememberCoroutineScope() + + BaseScaffold( + leadingIcon = Icons.Filled.Menu, + trailingIcon = icon, + onLeadingIconClick = { + val drawerState = scaffoldState.drawerState + scope.launch { + if (drawerState.isClosed) + drawerState.open() + else + drawerState.close() + } + }, + title = title, + navController = navController, + scaffoldState = scaffoldState, + bottomBar = { BottomBar(navController) }, + drawerContent = { + DrawerContent(navController, scaffoldState) + }, + content = content + ) +} + +@Composable +fun BaseScaffold( + leadingIcon: ImageVector, + trailingIcon: Painter?, + onLeadingIconClick: () -> Unit, + title: String, + navController: NavController, + scaffoldState: ScaffoldState, + bottomBar: @Composable () -> Unit, + drawerContent: @Composable (ColumnScope) -> Unit = {}, + content: (@Composable (it: PaddingValues) -> Unit), +) { + Scaffold( + scaffoldState = scaffoldState, + topBar = { + TopAppBar( + backgroundColor = mainBlue, + contentColor = Color.White + ) { + IconButton(onClick = onLeadingIconClick) { + Icon( + imageVector = leadingIcon, + contentDescription = "", + tint = white + ) + } + SpacerWidth(width = 5.dp) + Text( + text = title, + fontSize = 16.sp, + color = white + ) + if(trailingIcon != null) { + Spacer(Modifier.weight(1f)) + Box(Modifier.padding(15.dp, 15.dp)) { + Image(painter = trailingIcon, contentDescription = "icon") + } + } + } + }, + content = content, + drawerBackgroundColor = Color.Transparent, + drawerContentColor = Color.Transparent, + //drawerShape = drawerShape, + drawerElevation = 0.dp, + drawerContent = drawerContent, + bottomBar = bottomBar + ) +} + +@Composable +fun SettingsScaffold( + onLeave: () -> Unit, + scaffoldState: ScaffoldState, + navController: NavController, + content: @Composable (PaddingValues) -> Unit +) { + BaseScaffold( + leadingIcon = Icons.Filled.ArrowBack, + trailingIcon = R.drawable.ic_settings.AsPainter(), + onLeadingIconClick = onLeave, + title = R.string.title_settings.AsString(), + navController = navController, + scaffoldState = scaffoldState, + bottomBar = {}, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/DataPieChart.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/DataPieChart.kt new file mode 100644 index 0000000..ef51ef6 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/DataPieChart.kt @@ -0,0 +1,58 @@ +package ru.megboyzz.dnevnik.screens.ui.piechart + +import android.content.Context +import android.graphics.Typeface +import android.widget.LinearLayout +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.formatter.PercentFormatter + +class DataPieChart(context: Context) : PieChart(context) { + + init { + layoutParams = LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, + ) + setUsePercentValues(true) + setEntryLabelTextSize(10f) + + description.isEnabled = false + isDrawHoleEnabled = false + legend.isEnabled = false + legend.orientation = Legend.LegendOrientation.VERTICAL + legend.horizontalAlignment = + Legend.LegendHorizontalAlignment.CENTER + legend.textSize = 10f + + setEntryLabelColor(Color.Black.toArgb()) + } + + fun updatePieChartWithData(data: List) { + val entries = data.map { PieEntry(it.value.toFloat(), it.legend) } + + val ds = PieDataSet(entries, "") + + ds.colors = data.map { it.color.toArgb() } + + ds.yValuePosition = PieDataSet.ValuePosition.INSIDE_SLICE + ds.xValuePosition = PieDataSet.ValuePosition.INSIDE_SLICE + + ds.sliceSpace = 2f + + ds.valueTextColor = Color.Black.toArgb() + ds.valueFormatter = PercentFormatter(this) + ds.valueTextSize = 10f + ds.valueTypeface = Typeface.DEFAULT + + this.data = PieData(ds) + + invalidate() + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/PieChart.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/PieChart.kt new file mode 100644 index 0000000..aa73de4 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/piechart/PieChart.kt @@ -0,0 +1,84 @@ +package ru.megboyzz.dnevnik.screens.ui.piechart + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.dark +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.components.BaseCard +import ru.megboyzz.dnevnik.ui.components.EmptyMessage + +/** + * Класс для предоставления данных для круговых диаграм + * Идея предоставить диаграмме массив именованных данных + * напрмер кол-во оцеок отлично по всем предметам: + * CircleDiagramData(parameterName = "История", count=5) = 38% + * CircleDiagramData(parameterName = "Русский язык", count=4) = 31% + * CircleDiagramData(parameterName = "Химия", count=1) = 8% + * CircleDiagramData(parameterName = "Математика", count=3) = 23% + */ +data class PieChartData( + val legend: String, + val value: Int +){ + val color: Color + init { + val hue = (0..360).random(kotlin.random.Random(System.nanoTime())).toFloat() + Thread.sleep(10) + color = Color.hsl(hue, 0.5f, 0.6f) + } +} + +@Composable +fun PieChart( + diagramData: List +) { + lateinit var chart: DataPieChart + Column(Modifier.size(200.dp)) { + AndroidView( + factory = { context -> + chart = DataPieChart(context) + chart + }, + modifier = Modifier + .wrapContentSize() + .padding(5.dp), + update = { chart.updatePieChartWithData(diagramData) } + ) + } +} + + +@Composable +fun PieChartCard( + title: String, + data: List? +) { + BaseCard { + Column( + modifier = Modifier.padding(15.dp, 10.dp), + ){ + Text( + text = title, + style = H2, + color = dark + ) + Box( + modifier = Modifier.defaultMinSize(300.dp), + contentAlignment = Alignment.Center + ){ + if(data != null) + PieChart(diagramData = data) + else + EmptyMessage(message = R.string.title_no_marks.AsString()) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCalls.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCalls.kt new file mode 100644 index 0000000..88ac88e --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCalls.kt @@ -0,0 +1,51 @@ +package ru.megboyzz.dnevnik.screens.ui.schedule + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.components.BaseCard +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.dark +import java.util.* + + +//Веремнная сущность для определения пунктов карточки +data class CallData( + val startTime: String, + val endTime: String +) + +@Composable +fun BaseScheduleCallsCard( + callsList: List +) { + BaseCard{ + Column( + verticalArrangement = Arrangement.spacedBy(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(vertical = 20.dp).defaultMinSize(300.dp) + ) { + for(i in callsList.indices) + Text( + text = "${(i+1).displayTextInRightLocale()} -> ${callsList[i].startTime} - ${callsList[i].endTime}", + style = H2, + color = dark + ) + } + } +} + +@Composable +fun Int.displayTextInRightLocale(): String + = if(Locale.getDefault().country == "RU") + "$this ${R.string.title_lesson.AsString()}" + else + "${R.string.title_lesson.AsString()} $this" diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCard.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCard.kt new file mode 100644 index 0000000..130df05 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleCard.kt @@ -0,0 +1,52 @@ +package ru.megboyzz.dnevnik.screens.ui.schedule + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Divider +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import ru.megboyzz.dnevnik.ui.components.BaseCard +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.mainBlue +import ru.megboyzz.dnevnik.ui.theme.textSchedule + +@Composable +fun BaseScheduleCard( + dayTitle: String, + subjectNames: List +) { + BaseCard { + Column( + modifier = Modifier.padding(10.dp).padding(bottom = 15.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(5.dp), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = dayTitle, + style = H2, + color = mainBlue, + ) + Divider( + color = mainBlue, + thickness = 1.dp, + ) + } + for(i in subjectNames.indices){ + Text( + text = "${i+1}. ${subjectNames[i]}", + style = textSchedule + ) + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleShield.kt b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleShield.kt new file mode 100644 index 0000000..4eff080 --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/ui/components/schedule/ScheduleShield.kt @@ -0,0 +1,91 @@ +package ru.megboyzz.dnevnik.screens.ui.schedule + +import androidx.compose.foundation.layout.* +import androidx.compose.material.Icon +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import ru.megboyzz.dnevnik.ui.AsPainter +import ru.megboyzz.dnevnik.ui.AsString +import ru.megboyzz.dnevnik.R +import ru.megboyzz.dnevnik.ui.components.BaseCard +import ru.megboyzz.dnevnik.ui.theme.H2 +import ru.megboyzz.dnevnik.ui.theme.MainText +import ru.megboyzz.dnevnik.ui.theme.dark +import ru.megboyzz.dnevnik.ui.theme.mainBlue + +@Composable +fun BaseScheduleShield( + subjectName: String, + teacherName: String, + classNumber: String, + nextLessonName: String +) { + BaseCard { + Column( + modifier = Modifier + .defaultMinSize(300.dp) + .padding(top = 20.dp, bottom = 40.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(15.dp) + ) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon( + painter = R.drawable.ic_cap.AsPainter(), + contentDescription = "" + ) + + TextHeader( + title = R.string.title_now_lesson.AsString(), + value = subjectName + ) + } + TextHeader( + title = R.string.title_lesson_teacher.AsString(), + value = teacherName + ) + TextHeader( + title = R.string.title_classroom.AsString(), + value = classNumber + ) + TextHeader( + title = R.string.title_next_lesson.AsString(), + value = nextLessonName, + valueIsBlue = false + ) + } + } +} + +@Composable +fun TextHeader( + title: String, + value: String, + valueIsBlue: Boolean = true +) { + Column( + verticalArrangement = Arrangement.spacedBy(2.dp), + modifier = Modifier.width(200.dp) + ) { + Text( + text = title, + style = MainText, + color = dark, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + Text( + text = value, + style = H2, + color = if(valueIsBlue) mainBlue else dark, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt b/app/src/main/java/ru/megboyzz/dnevnik/util/Utils.kt similarity index 99% rename from app/src/main/java/ru/megboyzz/dnevnik/Utils.kt rename to app/src/main/java/ru/megboyzz/dnevnik/util/Utils.kt index 7dbdc4d..32ce736 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/Utils.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/util/Utils.kt @@ -1,4 +1,4 @@ -package ru.megboyzz.dnevnik +package ru.megboyzz.dnevnik.util import androidx.compose.foundation.clickable diff --git a/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateDeserializer.kt b/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateDeserializer.kt new file mode 100644 index 0000000..186d4dc --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateDeserializer.kt @@ -0,0 +1,15 @@ +package ru.megboyzz.dnevnik.util.gson + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import java.lang.reflect.Type +import java.util.* + +class DateDeserializer : JsonDeserializer { + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date { + val date = json.asString + return Date() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateSerializer.kt b/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateSerializer.kt new file mode 100644 index 0000000..ba79d2a --- /dev/null +++ b/app/src/main/java/ru/megboyzz/dnevnik/util/gson/DateSerializer.kt @@ -0,0 +1,15 @@ +package ru.megboyzz.dnevnik.util.gson + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type +import java.util.* + +class DateSerializer : JsonSerializer { + override fun serialize(date: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + val formattedDate = date?.time?.let { "/Date($it)/" } + return JsonPrimitive(formattedDate) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/AuthorizationViewModel.kt b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/AuthorizationViewModel.kt index 49a3110..f20e5f1 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/AuthorizationViewModel.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/AuthorizationViewModel.kt @@ -52,4 +52,10 @@ class AuthorizationViewModel @Inject constructor( } } + fun acceptAlert(){ + viewModelScope.launch(Dispatchers.IO) { + loginStatus.emit(AuthorizationStatus.NOTHING) + } + } + } diff --git a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/ProfileViewModel.kt b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/ProfileViewModel.kt index 2787248..862beb6 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/ProfileViewModel.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/ProfileViewModel.kt @@ -1,20 +1,16 @@ package ru.megboyzz.dnevnik.viewmodel import android.util.Log -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.db.dao.GlobalUserContextDao -import ru.megboyzz.dnevnik.entities.context.GroupType +import ru.megboyzz.dnevnik.entities.context.PeriodType 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 @Inject constructor( @@ -51,7 +47,7 @@ class ProfileViewModel @Inject constructor( lastName = info.lastName, groupName = personContext.group.name, term = term.number + 1, - isTerm = term.type == GroupType.Quarter, + isTerm = term.type == PeriodType.Quarter, avatarUrl = info.avatarUrl ) } diff --git a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/subscreen/LastMarksViewModel.kt b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/subscreen/LastMarksViewModel.kt index 43b761f..ca16211 100644 --- a/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/subscreen/LastMarksViewModel.kt +++ b/app/src/main/java/ru/megboyzz/dnevnik/viewmodel/subscreen/LastMarksViewModel.kt @@ -1,5 +1,6 @@ package ru.megboyzz.dnevnik.viewmodel.subscreen +import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers @@ -7,26 +8,112 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import ru.megboyzz.dnevnik.db.dao.CredentialsDao import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao -import ru.megboyzz.dnevnik.entities.response.UserContext +import ru.megboyzz.dnevnik.entities.context.SchoolType +import ru.megboyzz.dnevnik.entities.marks.RecentMarks +import ru.megboyzz.dnevnik.entities.teachers.Teacher import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel +import ru.megboyzz.dnevnik.service.ContextService +import ru.megboyzz.dnevnik.service.MarksService import javax.inject.Inject class LastMarksViewModel @Inject constructor( globalUserContextDao: GlobalUserContextDao, - credentialsDao: CredentialsDao + credentialsDao: CredentialsDao, + marksService: MarksService, + contextService: ContextService ): ViewModel() { val lastMarksList = MutableStateFlow(listOf()) val isLoading = MutableStateFlow(true) + val isError = MutableStateFlow(false) init{ viewModelScope.launch(Dispatchers.IO) { + if(globalUserContextDao.globalUserContextIsExists() && credentialsDao.credentialsIsExists()){ + val credentials = credentialsDao.getCredentials() + val userContext = globalUserContextDao.getGlobalUserContext() - globalUserContextDao.globalUserContextIsExists() + val personContext = userContext.apiUserContext.contextPersons.find { + it.school.type == SchoolType.Regular + } + if(personContext != null) { + val recentMarks = marksService.getRecentMarks( + credentials.token, + personContext.personId, + personContext.group.id + ) + + val teachers = contextService.getTeachers( + credentials.token, + personContext.group.id + ) + + val recentMarksResponse = recentMarks.execute() + val teachersResponse = teachers.execute() + + if(recentMarksResponse.isSuccessful && teachersResponse.isSuccessful){ + + val teachers = teachersResponse.body() + val recentMarksFinal = recentMarksResponse.body() + + if(teachers != null && recentMarksFinal != null){ + lastMarksList.emit(convertRecentMarksToListMarksModel( + teachers = teachers, + recentMarks = recentMarksFinal + )) + isLoading.emit(false) + } + + } + + + }else isError.emit(true) + } } } + fun convertRecentMarksToListMarksModel( + recentMarks: RecentMarks, + teachers: List + ): List{ + + val list = mutableListOf() + + recentMarks.marks.forEach { mark -> + + Log.i("Mark", mark.lessonId.toString()) + //TODO гавно с оценками NPE + val lesson = recentMarks.lessons.find { it.id == mark.lessonId }!! + + val teacher = teachers.find{ it.id == lesson.teachersId[0] } + + val subjectId = lesson.subjectId + + val workTypeId = recentMarks.works.find { it.id == mark.workId }?.workType + + val workType = recentMarks.workTypes.find { it.id == workTypeId } + + val subjectName = recentMarks.subjects.find { it.id == subjectId }?.name ?: "" + val reason = workType?.name ?: "null" + val markInModel = mark.value + + val markModel = MarkModel( + subjectName = subjectName, + mark = markInModel, + date = mark.date, + reason = reason, + teacher = teacher.toString() + ) + + list.add(markModel) + + } + + return list + + } + } \ No newline at end of file diff --git a/app/src/test/java/ru/megboyzz/dnevnik/ExampleUnitTest.kt b/app/src/test/java/ru/megboyzz/dnevnik/ExampleUnitTest.kt new file mode 100644 index 0000000..30a3c34 --- /dev/null +++ b/app/src/test/java/ru/megboyzz/dnevnik/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package ru.megboyzz.dnevnik + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 4350771..0dadbf9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ plugins { - id("com.android.application") version("7.2.1") apply(false) + id("com.android.application") version("7.3.1") apply(false) id("org.jetbrains.kotlin.android") version(Versions.kotlin) apply(false) } \ No newline at end of file diff --git a/buildSrc/src/main/java/AppDependencies.kt b/buildSrc/src/main/java/AppDependencies.kt index 0d71fa1..98cc76f 100644 --- a/buildSrc/src/main/java/AppDependencies.kt +++ b/buildSrc/src/main/java/AppDependencies.kt @@ -60,7 +60,7 @@ object AndroidTestLibraries: DependencyList() { //test libs val extJUnit = "androidx.test.ext:junit:${Versions.extJunit}" - val espressoCore = "androidx.test.espresso:espresso-core:${Versions.espresso}" + //val espressoCore = "androidx.test.espresso:espresso-core:${Versions.espresso}" } diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index bc4d62f..2cd9cef 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -19,7 +19,7 @@ object Versions { //test val junit = "4.12" val extJunit = "1.1.1" - val espresso = "3.2.0" + val espresso = "2.2.0" val desugarJdkVersion = "1.1.5" diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts new file mode 100644 index 0000000..307ad3d --- /dev/null +++ b/data/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.kotlinAndroid) + alias(libs.plugins.ksp) +} + +android { + namespace = "ru.megboyzz.data" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + ksp(libs.room.compiler) + ksp(libs.dagger.compiler) + + implementation(libs.room.ktx) + implementation(libs.room.runtime) + + implementation(libs.dagger) + + implementation(libs.retrofit) + implementation(libs.retrofit.gson) + implementation(libs.jsoup) + + implementation(libs.core.ktx) + implementation(libs.appcompat) + implementation(libs.material) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.espresso.core) + + implementation(project(":domain")) +} \ No newline at end of file diff --git a/data/consumer-rules.pro b/data/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/data/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/data/src/androidTest/java/ru/megboyzz/data/ExampleInstrumentedTest.kt b/data/src/androidTest/java/ru/megboyzz/data/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..447e3a6 --- /dev/null +++ b/data/src/androidTest/java/ru/megboyzz/data/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package ru.megboyzz.data + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("ru.megboyzz.data.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/data/src/main/AndroidManifest.xml b/data/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/data/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationManager.kt b/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationManager.kt new file mode 100644 index 0000000..ab079bf --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationManager.kt @@ -0,0 +1,153 @@ +package ru.megboyzz.dnevnik.authorization + +import android.util.Log +import com.google.gson.Gson +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.jsoup.Connection +import org.jsoup.HttpStatusException +import org.jsoup.Jsoup +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.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 @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 marksState = "https://dnevnik.ru/marks" + + private var credentials = Credentials(token = "", cookies = mapOf(), rememberMe = false) + + private val gson = Gson() + + fun login( + login: String, + password: String, + rememberMe: Boolean + ): AuthorizationStatus { + + runCatching { + Jsoup + .connect(baseUrl) + .method(Connection.Method.POST) + .data("ReturnUrl", returnUrl) + .data("login", login) + .data("password", password) + .followRedirects(true) + .execute() + }.onFailure { + + if(it is HttpStatusException && it.statusCode == 405) + return AuthorizationStatus.MAINTAIN + + return AuthorizationStatus.NO_INTERNET + + }.onSuccess { response -> + if(response.statusCode() != 200) + return AuthorizationStatus.MAINTAIN + + val url = response.url() + + if(url.getResult() != "success") + return AuthorizationStatus.WRONG_CREDENTIALS + + //Безопасная зона + val cookies = response.cookies() + credentials = Credentials( + token = url.getToken(), + cookies = cookies, + rememberMe = rememberMe + ) + dbUpdate() + + kotlin.runCatching { + + Jsoup + .connect(marksState) + .method(Connection.Method.GET) + .cookies(cookies) + .ignoreContentType(true) + .followRedirects(true) + .execute() + + }.onFailure { + Log.e("Auth", it.message.toString()) + return AuthorizationStatus.NO_INTERNET + }.onSuccess { response1 -> + val htmlByLines = response1.body().split("\n") + val contextLine = htmlByLines.find { it.contains("window.__MARKS__INITIAL__STATE__") } + val marksContextStr = + contextLine?.substring(contextLine.indexOf("{"), contextLine.length - 2) + val marksContext = gson.fromJson(marksContextStr, MarksContext::class.java) + + val context = contextService.getContext(credentials.token, marksContext.context.userId).execute() + + + + if(context.isSuccessful){ + val body = context.body() ?: return AuthorizationStatus.UNKNOWN_ERROR + globalUserContextDao.setGlobalUserContext(GlobalUserContext( + apiUserContext = body, + marksInitStateUserContext = marksContext + )) + } else return AuthorizationStatus.UNKNOWN_ERROR + } + + } + + return AuthorizationStatus.LOGGED + + } + + fun checkCredentials(): AuthorizationStatus{ + + val credentialsIsExists = credentialsDao.credentialsIsExists() + return if(credentialsIsExists) + AuthorizationStatus.LOGGED + else + AuthorizationStatus.NOTHING + + } + + fun dbUpdate(){ + credentialsDao.setCredentials(credentials) + } + + fun deleteCredentials(){ + credentialsDao.deleteCredentials() + } + + fun URL.getResult(): String{ + + val httpUrl = this.toString().toHttpUrlOrNull() ?: return "" + + return httpUrl.queryParameter("result") ?: "" + + } + + fun URL.getToken(): String{ + + val tokenRegex = "access_token=([A-Za-z0-9]+)" + val regex = Regex(tokenRegex) + val find = regex.find(this.toString()) + if(find != null) + return find.groupValues[1] + return "" + + } + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationStatus.kt b/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationStatus.kt new file mode 100644 index 0000000..e3e72f6 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/authorization/AuthorizationStatus.kt @@ -0,0 +1,15 @@ +package ru.megboyzz.dnevnik.authorization + +enum class AuthorizationStatus { + + LOGGED, + LOGGING, + WRONG_CREDENTIALS, + NO_INTERNET, + MAINTAIN, + CREDENTIALS_LOADING, + NOTHING, + UNKNOWN_ERROR, + LOGOUT + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/db/AppDataBase.kt b/data/src/main/java/ru/megboyzz/data/db/AppDataBase.kt new file mode 100644 index 0000000..dc24a16 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/db/AppDataBase.kt @@ -0,0 +1,19 @@ +package ru.megboyzz.dnevnik.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import ru.megboyzz.dnevnik.db.dao.CredentialsDao +import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao +import ru.megboyzz.dnevnik.entities.db.Credentials +import ru.megboyzz.dnevnik.entities.db.GlobalUserContext + +@Database( + entities = [Credentials::class, GlobalUserContext::class], + version = 1 +) +abstract class AppDataBase: RoomDatabase() { + + abstract fun credentialsDao(): CredentialsDao + abstract fun globalUserContextDao(): GlobalUserContextDao + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/db/converters/CookiesConverter.kt b/data/src/main/java/ru/megboyzz/data/db/converters/CookiesConverter.kt new file mode 100644 index 0000000..fe13b30 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/db/converters/CookiesConverter.kt @@ -0,0 +1,18 @@ +package ru.megboyzz.dnevnik.db.converters + +import androidx.room.TypeConverter +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class CookiesConverter { + + private val gson = Gson() + private val mapType = object : TypeToken>() {}.type + + @TypeConverter + fun fromMap(cookies: Map): String = gson.toJson(cookies) + + @TypeConverter + fun toMap(savedCookies: String): Map = gson.fromJson(savedCookies, mapType) + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/db/converters/DataStructureConverter.kt b/data/src/main/java/ru/megboyzz/data/db/converters/DataStructureConverter.kt new file mode 100644 index 0000000..6e5b850 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/db/converters/DataStructureConverter.kt @@ -0,0 +1,24 @@ +package ru.megboyzz.dnevnik.db.converters + +import androidx.room.TypeConverter +import com.google.gson.Gson +import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext +import ru.megboyzz.dnevnik.entities.response.UserContext + +class DataStructureConverter { + + val gson = Gson() + + @TypeConverter + fun fromUserContext(userContext: UserContext): String = gson.toJson(userContext) + + @TypeConverter + fun toUserContext(savedUserContext: String): UserContext = gson.fromJson(savedUserContext, UserContext::class.java) + + @TypeConverter + fun fromMarksContext(marksContext: MarksContext): String = gson.toJson(marksContext) + + @TypeConverter + fun toMarksContext(savedMarksContext: String): MarksContext = gson.fromJson(savedMarksContext, MarksContext::class.java) + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/db/dao/CredentialsDao.kt b/data/src/main/java/ru/megboyzz/data/db/dao/CredentialsDao.kt new file mode 100644 index 0000000..182bbef --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/db/dao/CredentialsDao.kt @@ -0,0 +1,24 @@ +package ru.megboyzz.dnevnik.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import ru.megboyzz.dnevnik.entities.db.Credentials + +@Dao +interface CredentialsDao { + + @Query("SELECT EXISTS(SELECT * FROM Credentials WHERE id = 0)") + fun credentialsIsExists(): Boolean + @Query("SELECT * FROM Credentials WHERE id = 0") + fun getCredentials(): Credentials + + @Query("DELETE FROM Credentials WHERE id = 0") + fun deleteCredentials() + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun setCredentials(profile: Credentials) + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/db/dao/GlobalUserContextDao.kt b/data/src/main/java/ru/megboyzz/data/db/dao/GlobalUserContextDao.kt new file mode 100644 index 0000000..654a6dc --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/db/dao/GlobalUserContextDao.kt @@ -0,0 +1,24 @@ +package ru.megboyzz.dnevnik.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import ru.megboyzz.dnevnik.entities.db.GlobalUserContext + +@Dao +interface GlobalUserContextDao { + + @Query("SELECT EXISTS(SELECT * FROM GlobalUserContext WHERE id = 0)") + fun globalUserContextIsExists(): Boolean + + @Query("SELECT * FROM GlobalUserContext WHERE id = 0") + fun getGlobalUserContext(): GlobalUserContext + + @Query("DELETE FROM GlobalUserContext WHERE id = 0") + fun deleteGlobalUserContext() + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun setGlobalUserContext(profile: GlobalUserContext) + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/di/AppComponent.kt b/data/src/main/java/ru/megboyzz/data/di/AppComponent.kt new file mode 100644 index 0000000..6ef3036 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/di/AppComponent.kt @@ -0,0 +1,42 @@ +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.entities.db.Credentials +import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel +import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel +import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel +import javax.inject.Singleton + + +@Singleton +@Component(modules = [AppModule::class]) +interface AppComponent { + + val authViewModel: AuthorizationViewModel + + val profileViewModel: ProfileViewModel + + val lastMarksViewModel: LastMarksViewModel + + val credentialsDao: CredentialsDao + + val globalUserContextDao: GlobalUserContextDao + fun inject(viewModel: ProfileViewModel) + fun inject(activity: MainActivity) + + @Component.Builder + interface Builder { + fun appModule(appModule: AppModule): Builder // add this + fun build(): AppComponent + } + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/di/modules/ApiModule.kt b/data/src/main/java/ru/megboyzz/data/di/modules/ApiModule.kt new file mode 100644 index 0000000..7352c9f --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/di/modules/ApiModule.kt @@ -0,0 +1,42 @@ +package ru.megboyzz.dnevnik.di.modules + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import dagger.Module +import dagger.Provides +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.create +import ru.megboyzz.dnevnik.service.ContextService +import ru.megboyzz.dnevnik.service.MarksService +import ru.megboyzz.dnevnik.util.gson.DateDeserializer +import ru.megboyzz.dnevnik.util.gson.DateSerializer +import java.util.* + +@Module +class ApiModule { + + private val apiUrl = "https://api.dnevnik.ru" + + @Provides + fun provideApiRetrofit(gson: Gson): Retrofit{ + return Retrofit + .Builder() + .baseUrl(apiUrl) + .addConverterFactory(GsonConverterFactory.create(gson)) + .build() + } + + @Provides + fun provideGson(): Gson = GsonBuilder() + //.registerTypeAdapter(Date::class.java, DateSerializer()) + .registerTypeAdapter(Date::class.java, DateDeserializer()) + .create() + + @Provides + fun provideContextService(retrofit: Retrofit): ContextService = retrofit.create() + + @Provides + fun provideMarksService(retrofit: Retrofit): MarksService = retrofit.create() + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/di/modules/AppModule.kt b/data/src/main/java/ru/megboyzz/data/di/modules/AppModule.kt new file mode 100644 index 0000000..b7577be --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/di/modules/AppModule.kt @@ -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 + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/di/modules/AuthModule.kt b/data/src/main/java/ru/megboyzz/data/di/modules/AuthModule.kt new file mode 100644 index 0000000..97dece1 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/di/modules/AuthModule.kt @@ -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 { + + + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/di/modules/DatabaseModule.kt b/data/src/main/java/ru/megboyzz/data/di/modules/DatabaseModule.kt new file mode 100644 index 0000000..dc16859 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/di/modules/DatabaseModule.kt @@ -0,0 +1,33 @@ +package ru.megboyzz.dnevnik.di.modules + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.coroutineScope +import ru.megboyzz.dnevnik.db.AppDataBase +import ru.megboyzz.dnevnik.db.dao.CredentialsDao +import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao +import ru.megboyzz.dnevnik.entities.db.Credentials +import javax.inject.Singleton +import kotlin.coroutines.coroutineContext + +@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() + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/service/ContextService.kt b/data/src/main/java/ru/megboyzz/data/service/ContextService.kt new file mode 100644 index 0000000..af7b602 --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/service/ContextService.kt @@ -0,0 +1,19 @@ +package ru.megboyzz.dnevnik.service + +import retrofit2.Call +import retrofit2.http.* +import ru.megboyzz.dnevnik.entities.response.UserContext +import ru.megboyzz.dnevnik.entities.teachers.Teacher + +interface ContextService{ + + @GET("/mobile/v4/users/{personId}/context") + fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call + + @GET("/v1/edu-groups/{groupId}/teachers") + fun getTeachers( + @Header("Access-Token") accessToken: String, + @Path("groupId") groupId: Long + ): Call> + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/service/MarksService.kt b/data/src/main/java/ru/megboyzz/data/service/MarksService.kt new file mode 100644 index 0000000..3d6fcec --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/service/MarksService.kt @@ -0,0 +1,22 @@ +package ru.megboyzz.dnevnik.service + +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Path +import ru.megboyzz.dnevnik.entities.marks.RecentMarks + +//Сервис для последних оценок +interface MarksService { + + @GET("/v2/persons/{personId}/group/{groupId}/recentmarks") + fun getRecentMarks( + @Header("Access-Token") accessToken: String, + @Path("personId") personId: Long, + @Path("groupId") groupId: Long + ): Call + + + + +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/util/gson/DateDeserializer.kt b/data/src/main/java/ru/megboyzz/data/util/gson/DateDeserializer.kt new file mode 100644 index 0000000..186d4dc --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/util/gson/DateDeserializer.kt @@ -0,0 +1,15 @@ +package ru.megboyzz.dnevnik.util.gson + +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import java.lang.reflect.Type +import java.util.* + +class DateDeserializer : JsonDeserializer { + override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date { + val date = json.asString + return Date() + } +} \ No newline at end of file diff --git a/data/src/main/java/ru/megboyzz/data/util/gson/DateSerializer.kt b/data/src/main/java/ru/megboyzz/data/util/gson/DateSerializer.kt new file mode 100644 index 0000000..ba79d2a --- /dev/null +++ b/data/src/main/java/ru/megboyzz/data/util/gson/DateSerializer.kt @@ -0,0 +1,15 @@ +package ru.megboyzz.dnevnik.util.gson + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer +import java.lang.reflect.Type +import java.util.* + +class DateSerializer : JsonSerializer { + override fun serialize(date: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { + val formattedDate = date?.time?.let { "/Date($it)/" } + return JsonPrimitive(formattedDate) + } +} \ No newline at end of file diff --git a/data/src/test/java/ru/megboyzz/data/ExampleUnitTest.kt b/data/src/test/java/ru/megboyzz/data/ExampleUnitTest.kt new file mode 100644 index 0000000..8666241 --- /dev/null +++ b/data/src/test/java/ru/megboyzz/data/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package ru.megboyzz.data + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/domain/.gitignore b/domain/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/domain/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts new file mode 100644 index 0000000..7afdbc4 --- /dev/null +++ b/domain/build.gradle.kts @@ -0,0 +1,10 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + id("java-library") + alias(libs.plugins.org.jetbrains.kotlin.jvm) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 +} \ No newline at end of file diff --git a/domain/src/main/java/ru/megboyzz/domain/MyClass.kt b/domain/src/main/java/ru/megboyzz/domain/MyClass.kt new file mode 100644 index 0000000..378f49f --- /dev/null +++ b/domain/src/main/java/ru/megboyzz/domain/MyClass.kt @@ -0,0 +1,4 @@ +package ru.megboyzz.domain + +class MyClass { +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..5868bcd --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,71 @@ +[versions] +agp = "8.3.0-alpha05" +kotlin = "1.9.0" +core-ktx = "1.12.0" +junit = "4.13.2" +androidx-test-ext-junit = "1.1.5" +espresso-core = "3.5.1" +lifecycle-runtime-ktx = "2.6.2" +activity-compose = "1.7.2" +compose-bom = "2023.08.00" +dagger = "2.48" +pieChartLib = "v3.1.0" +retrofit = "2.6.4" +retrofit_gson = "2.3.0" +calendar = "2.1.1" +coli = "2.2.0" +jsoup = "1.15.4" +ksp = "1.9.0-1.0.12" +voyager = "1.0.0-rc05" +room = "2.5.0" +appcompat = "1.6.1" +material = "1.10.0" +org-jetbrains-kotlin-jvm = "1.9.0" + +[libraries] +core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } +espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } +lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } +activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } +compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } +ui = { group = "androidx.compose.ui", name = "ui" } +ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +material3 = { group = "androidx.compose.material3", name = "material3" } + +# UI +voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } +voyager-bottomSheetNavigator = { module = "cafe.adriel.voyager:voyager-bottom-sheet-navigator", version.ref = "voyager" } +voyager-tabNavigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } +voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } +voyager-androidx = { module = "cafe.adriel.voyager:voyager-androidx", version.ref = "voyager" } +pie-chart-lib = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "pieChartLib" } +calendar = { module = "com.kizitonwose.calendar:compose", version.ref = "calendar" } +coli = { module = "io.coil-kt:coil-compose", version.ref= "coli" } +appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +icons = { group = "androidx.compose.material", name = "material-icons-extended", version = "1.5.3" } +desugar-jdk = { group = "com.android.tools", name = "desugar_jdk_libs", version = "1.1.5" } + +# data +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +retrofit-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit_gson" } +room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } +#ksp +room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" } + +[plugins] +androidApplication = { id = "com.android.application", version.ref = "agp" } +kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +androidLibrary = { id = "com.android.library", version.ref = "agp" } +org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0a7de7f..fc851f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Apr 07 23:25:54 MSK 2023 +#Mon Apr 24 20:13:31 MSK 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME