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