last commit
This commit is contained in:
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="11" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-20
@@ -1,20 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
<option value="$PROJECT_DIR$/buildSrc" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<option name="composableFile" value="true" />
|
|
||||||
<option name="previewFile" value="true" />
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
Generated
-30
@@ -1,30 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RemoteRepositoriesConfiguration">
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="central" />
|
|
||||||
<option name="name" value="Maven Central repository" />
|
|
||||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="jboss.community" />
|
|
||||||
<option name="name" value="JBoss Community repository" />
|
|
||||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="MavenRepo" />
|
|
||||||
<option name="name" value="MavenRepo" />
|
|
||||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="maven" />
|
|
||||||
<option name="name" value="maven" />
|
|
||||||
<option name="url" value="https://jitpack.io" />
|
|
||||||
</remote-repository>
|
|
||||||
<remote-repository>
|
|
||||||
<option name="id" value="Google" />
|
|
||||||
<option name="name" value="Google" />
|
|
||||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
|
||||||
</remote-repository>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-17
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
<component name="SuppressABINotification">
|
|
||||||
<option name="modulesWithSuppressedNotConfigured">
|
|
||||||
<set>
|
|
||||||
<option value="MyDnevnik.app" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.example.myapplication;
|
||||||
|
|
||||||
|
public interface ResultAction {
|
||||||
|
void action();
|
||||||
|
}
|
||||||
@@ -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<Integer, ResultAction> 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(("
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-4
@@ -32,11 +32,11 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "11"
|
||||||
freeCompilerArgs = listOf(
|
freeCompilerArgs = listOf(
|
||||||
"-Xjvm-default=compatibility"
|
"-Xjvm-default=compatibility"
|
||||||
)
|
)
|
||||||
@@ -57,8 +57,20 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("connectToWSA"){
|
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("connectToWSA")
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
named("connectToWSA") {
|
||||||
|
exec {
|
||||||
|
commandLine = "adb connect localhost:58526".split(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.util.Log
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import org.jsoup.Connection
|
import org.jsoup.Connection
|
||||||
|
import org.jsoup.HttpStatusException
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||||
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
||||||
@@ -49,7 +50,12 @@ class AuthorizationManager @Inject constructor(
|
|||||||
.followRedirects(true)
|
.followRedirects(true)
|
||||||
.execute()
|
.execute()
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
|
|
||||||
|
if(it is HttpStatusException && it.statusCode == 405)
|
||||||
|
return AuthorizationStatus.MAINTAIN
|
||||||
|
|
||||||
return AuthorizationStatus.NO_INTERNET
|
return AuthorizationStatus.NO_INTERNET
|
||||||
|
|
||||||
}.onSuccess { response ->
|
}.onSuccess { response ->
|
||||||
if(response.statusCode() != 200)
|
if(response.statusCode() != 200)
|
||||||
return AuthorizationStatus.MAINTAIN
|
return AuthorizationStatus.MAINTAIN
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ru.megboyzz.dnevnik.di.modules.AppModule
|
|||||||
import ru.megboyzz.dnevnik.entities.db.Credentials
|
import ru.megboyzz.dnevnik.entities.db.Credentials
|
||||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||||
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
||||||
|
import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ interface AppComponent {
|
|||||||
|
|
||||||
val profileViewModel: ProfileViewModel
|
val profileViewModel: ProfileViewModel
|
||||||
|
|
||||||
|
val lastMarksViewModel: LastMarksViewModel
|
||||||
|
|
||||||
val credentialsDao: CredentialsDao
|
val credentialsDao: CredentialsDao
|
||||||
|
|
||||||
val globalUserContextDao: GlobalUserContextDao
|
val globalUserContextDao: GlobalUserContextDao
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package ru.megboyzz.dnevnik.di.modules
|
package ru.megboyzz.dnevnik.di.modules
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import retrofit2.create
|
||||||
import ru.megboyzz.dnevnik.service.ContextService
|
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
|
@Module
|
||||||
class ApiModule {
|
class ApiModule {
|
||||||
@@ -12,17 +19,24 @@ class ApiModule {
|
|||||||
private val apiUrl = "https://api.dnevnik.ru"
|
private val apiUrl = "https://api.dnevnik.ru"
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideApiRetrofit(): Retrofit{
|
fun provideApiRetrofit(gson: Gson): Retrofit{
|
||||||
return Retrofit
|
return Retrofit
|
||||||
.Builder()
|
.Builder()
|
||||||
.baseUrl(apiUrl)
|
.baseUrl(apiUrl)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideContextService(retrofit: Retrofit): ContextService{
|
fun provideGson(): Gson = GsonBuilder()
|
||||||
return retrofit.create(ContextService::class.java)
|
//.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()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,6 @@ package ru.megboyzz.dnevnik.entities.context
|
|||||||
|
|
||||||
data class PeriodGroup(
|
data class PeriodGroup(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val type: GroupType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них????
|
val type: PeriodType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них????
|
||||||
val periods: List<PeriodInfo>
|
val periods: List<PeriodInfo>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package ru.megboyzz.dnevnik.entities.context
|
|||||||
data class PeriodInfo(
|
data class PeriodInfo(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val number: Short,
|
val number: Short,
|
||||||
val type: GroupType,
|
val type: PeriodType,
|
||||||
val dateStart: Long,
|
val dateStart: Long,
|
||||||
val dateFinish: Long,
|
val dateFinish: Long,
|
||||||
val studyYear: Short,
|
val studyYear: Short,
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
package ru.megboyzz.dnevnik.entities.context
|
package ru.megboyzz.dnevnik.entities.context
|
||||||
|
|
||||||
enum class GroupType {
|
enum class PeriodType {
|
||||||
Quarter
|
Quarter
|
||||||
}
|
}
|
||||||
@@ -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<Long>,
|
||||||
|
@SerializedName("teachers")
|
||||||
|
val teachersId: List<Long>
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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<Mark>,
|
||||||
|
val works: List<Work>,
|
||||||
|
val subjects: List<Subject>,
|
||||||
|
val workTypes: List<WorkType>,
|
||||||
|
val lessons: List<Lesson>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.megboyzz.dnevnik.entities.subjects
|
||||||
|
|
||||||
|
data class Subject(
|
||||||
|
val id: Long,
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
@@ -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()}."
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ru.megboyzz.dnevnik.entities.teachers
|
||||||
|
|
||||||
|
enum class TeacherSex {
|
||||||
|
Male,
|
||||||
|
Female
|
||||||
|
}
|
||||||
@@ -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<Int>?
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -10,12 +10,12 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import ru.megboyzz.dnevnik.MainActivity
|
import ru.megboyzz.dnevnik.MainActivity
|
||||||
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
||||||
import ru.megboyzz.dnevnik.collectAsMutableState
|
|
||||||
import ru.megboyzz.dnevnik.screens.*
|
import ru.megboyzz.dnevnik.screens.*
|
||||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||||
import ru.megboyzz.dnevnik.navigate
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import ru.megboyzz.dnevnik.appComponent
|
import ru.megboyzz.dnevnik.appComponent
|
||||||
|
import ru.megboyzz.dnevnik.util.collectAsMutableState
|
||||||
|
import ru.megboyzz.dnevnik.util.navigate
|
||||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||||
|
|
||||||
sealed class BaseNavRote(open val route: String)
|
sealed class BaseNavRote(open val route: String)
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
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.R
|
||||||
import ru.megboyzz.dnevnik.screens.*
|
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.SubScreenData
|
||||||
import ru.megboyzz.dnevnik.screens.ui.SubScreenNavBar
|
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")
|
object ByYearMarks: MarksNavRoute("by_terms_marks")
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MarksSubScreenNavHost() {
|
fun MarksSubScreenNavHost() {
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ fun MarksSubScreenNavHost() {
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = MarksNavRoute.LastMarks.route
|
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.ByDaysMarks.route) { Text(MarksNavRoute.ByDaysMarks.route) }
|
||||||
composable(MarksNavRoute.StatsByMarks.route) { Text(MarksNavRoute.StatsByMarks.route) }
|
composable(MarksNavRoute.StatsByMarks.route) { Text(MarksNavRoute.StatsByMarks.route) }
|
||||||
composable(MarksNavRoute.StatsBySubjects.route) { Text(MarksNavRoute.StatsBySubjects.route) }
|
composable(MarksNavRoute.StatsBySubjects.route) { Text(MarksNavRoute.StatsBySubjects.route) }
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
|||||||
import androidx.compose.material.ScaffoldState
|
import androidx.compose.material.ScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import kotlinx.coroutines.launch
|
|||||||
import ru.megboyzz.dnevnik.MainActivity
|
import ru.megboyzz.dnevnik.MainActivity
|
||||||
import ru.megboyzz.dnevnik.appComponent
|
import ru.megboyzz.dnevnik.appComponent
|
||||||
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
||||||
import ru.megboyzz.dnevnik.collectAsMutableState
|
import ru.megboyzz.dnevnik.util.collectAsMutableState
|
||||||
import ru.megboyzz.dnevnik.navigate
|
import ru.megboyzz.dnevnik.util.navigate
|
||||||
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
||||||
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
|
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
|
||||||
import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent
|
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){
|
if(loginStatus.value == AuthorizationStatus.NO_INTERNET){
|
||||||
|
isLoading = false
|
||||||
AlertMessageBox(
|
AlertMessageBox(
|
||||||
title = "Информация",
|
title = "Информация",
|
||||||
alertText = "Отсутствует подключение к сети",
|
alertText = "Отсутствует подключение к сети",
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
|||||||
import androidx.compose.material.ScaffoldState
|
import androidx.compose.material.ScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.navigation.MarksSubScreenNavHost
|
import ru.megboyzz.dnevnik.navigation.MarksSubScreenNavHost
|
||||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
|||||||
import androidx.compose.material.ScaffoldState
|
import androidx.compose.material.ScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
|||||||
import ru.megboyzz.dnevnik.screens.ui.UnderlinedText
|
import ru.megboyzz.dnevnik.screens.ui.UnderlinedText
|
||||||
import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold
|
import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold
|
||||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||||
|
import ru.megboyzz.dnevnik.util.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
|
|||||||
@@ -1,2 +1,9 @@
|
|||||||
package ru.megboyzz.dnevnik.screens.subscreens
|
package ru.megboyzz.dnevnik.screens.subscreens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ByDayMarksScreen() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,2 +1,57 @@
|
|||||||
package ru.megboyzz.dnevnik.screens.subscreens
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ import java.util.*
|
|||||||
|
|
||||||
data class MarkModel(
|
data class MarkModel(
|
||||||
val subjectName: String,
|
val subjectName: String,
|
||||||
val mark: Int,
|
val mark: String,
|
||||||
val date: Date,
|
val date: Date,
|
||||||
val teacher: String,
|
val teacher: String,
|
||||||
val reason: String = "null"
|
val reason: String = "null"
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ import ru.megboyzz.dnevnik.*
|
|||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel
|
import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel
|
||||||
import ru.megboyzz.dnevnik.ui.theme.*
|
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
|
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ import ru.megboyzz.dnevnik.ui.theme.MainText
|
|||||||
import ru.megboyzz.dnevnik.ui.theme.Mini
|
import ru.megboyzz.dnevnik.ui.theme.Mini
|
||||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||||
import ru.megboyzz.dnevnik.R
|
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
|
@Composable
|
||||||
fun LoginScreenContent(
|
fun LoginScreenContent(
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.kizitonwose.calendar.core.daysOfWeek
|
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.navigation.AppNavRoute
|
||||||
import ru.megboyzz.dnevnik.screens.SettingsScreen
|
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.*
|
||||||
import ru.megboyzz.dnevnik.screens.ui.calendar.NiceCalendar
|
import ru.megboyzz.dnevnik.screens.ui.calendar.NiceCalendar
|
||||||
import ru.megboyzz.dnevnik.screens.ui.main.DrawerContent
|
import ru.megboyzz.dnevnik.screens.ui.main.DrawerContent
|
||||||
@@ -386,8 +387,13 @@ fun Test1() {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AlertTest() {
|
fun AlertTest() {
|
||||||
LeaveAlert(onAgree = { /*TODO*/ }) {
|
|
||||||
|
LastMarkCard(markModel = MarkModel(
|
||||||
}
|
subjectName = "Обществознание",
|
||||||
|
mark = 4.toString(),
|
||||||
|
teacher = "Расита",
|
||||||
|
reason = "Ответ на уроке",
|
||||||
|
date = Date("2023-04-13T00:00:00")
|
||||||
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
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.navigation.BaseNavRote
|
||||||
import ru.megboyzz.dnevnik.ui.theme.MainText
|
import ru.megboyzz.dnevnik.ui.theme.MainText
|
||||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||||
@@ -27,7 +27,7 @@ import ru.megboyzz.dnevnik.ui.theme.white
|
|||||||
fun SubScreenNavButton(
|
fun SubScreenNavButton(
|
||||||
text: String,
|
text: String,
|
||||||
isClicked: MutableState<Boolean>,
|
isClicked: MutableState<Boolean>,
|
||||||
onCLick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val colors = ButtonDefaults.buttonColors(
|
val colors = ButtonDefaults.buttonColors(
|
||||||
backgroundColor = if(isClicked.value) mainBlue else white,
|
backgroundColor = if(isClicked.value) mainBlue else white,
|
||||||
@@ -36,7 +36,7 @@ fun SubScreenNavButton(
|
|||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
isClicked.value = !isClicked.value
|
isClicked.value = !isClicked.value
|
||||||
onCLick.invoke()
|
onClick.invoke()
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
border = BorderStroke(
|
border = BorderStroke(
|
||||||
@@ -80,8 +80,9 @@ fun SubScreenNavBar(
|
|||||||
isClicked = data.isClicked
|
isClicked = data.isClicked
|
||||||
) {
|
) {
|
||||||
for(d in list){
|
for(d in list){
|
||||||
if(data != d)
|
if(data != d) {
|
||||||
d.isClicked.value = false
|
d.isClicked.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import kotlinx.coroutines.launch
|
|||||||
import ru.megboyzz.dnevnik.*
|
import ru.megboyzz.dnevnik.*
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.ui.theme.*
|
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.DayOfWeek
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import ru.megboyzz.dnevnik.ui.theme.MainText
|
|||||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||||
import ru.megboyzz.dnevnik.ui.theme.white
|
import ru.megboyzz.dnevnik.ui.theme.white
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
|
import ru.megboyzz.dnevnik.util.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BottomBar(navController: NavController) {
|
fun BottomBar(navController: NavController) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import ru.megboyzz.dnevnik.screens.ui.ProfileCard
|
|||||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||||
import ru.megboyzz.dnevnik.ui.theme.white
|
import ru.megboyzz.dnevnik.ui.theme.white
|
||||||
|
import ru.megboyzz.dnevnik.util.*
|
||||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||||
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
||||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.SpacerWidth
|
import ru.megboyzz.dnevnik.util.SpacerWidth
|
||||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||||
import ru.megboyzz.dnevnik.ui.theme.white
|
import ru.megboyzz.dnevnik.ui.theme.white
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package ru.megboyzz.dnevnik.screens.ui.piechart
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
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.H2
|
||||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
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.R
|
||||||
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
||||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ru.megboyzz.dnevnik.AsPainter
|
import ru.megboyzz.dnevnik.util.AsPainter
|
||||||
import ru.megboyzz.dnevnik.AsString
|
import ru.megboyzz.dnevnik.util.AsString
|
||||||
import ru.megboyzz.dnevnik.R
|
import ru.megboyzz.dnevnik.R
|
||||||
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
||||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||||
|
|||||||
@@ -3,10 +3,17 @@ package ru.megboyzz.dnevnik.service
|
|||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
import ru.megboyzz.dnevnik.entities.response.UserContext
|
import ru.megboyzz.dnevnik.entities.response.UserContext
|
||||||
|
import ru.megboyzz.dnevnik.entities.teachers.Teacher
|
||||||
|
|
||||||
interface ContextService{
|
interface ContextService{
|
||||||
|
|
||||||
@GET("/mobile/v4/users/{personId}/context")
|
@GET("/mobile/v4/users/{personId}/context")
|
||||||
fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call<UserContext>
|
fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call<UserContext>
|
||||||
|
|
||||||
|
@GET("/v1/edu-groups/{groupId}/teachers")
|
||||||
|
fun getTeachers(
|
||||||
|
@Header("Access-Token") accessToken: String,
|
||||||
|
@Path("groupId") groupId: Long
|
||||||
|
): Call<List<Teacher>>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
package ru.megboyzz.dnevnik.service
|
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 {
|
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<RecentMarks>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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<WeekDay>()
|
||||||
|
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<T>(
|
||||||
|
private val state: State<T>,
|
||||||
|
private val mutate: (T) -> Unit
|
||||||
|
) : MutableState<T> {
|
||||||
|
|
||||||
|
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 <T> MutableStateFlow<T>.collectAsMutableState(
|
||||||
|
context: CoroutineContext = EmptyCoroutineContext
|
||||||
|
): MutableState<T> = MutableStateAdapter(
|
||||||
|
state = collectAsState(context),
|
||||||
|
mutate = { value = it }
|
||||||
|
)
|
||||||
|
|
||||||
@@ -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<Int>?,
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
))
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Boolean>,
|
||||||
|
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<Boolean>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubScreenNavBar(
|
||||||
|
list: List<SubScreenData>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<LocalDate>() }
|
||||||
|
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<Week?>(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>): 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<DayOfWeek>) {
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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<PieChartData>) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<PieChartData>
|
||||||
|
) {
|
||||||
|
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<PieChartData>?
|
||||||
|
) {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<CallData>
|
||||||
|
) {
|
||||||
|
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"
|
||||||
@@ -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<String>
|
||||||
|
) {
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package ru.megboyzz.dnevnik
|
package ru.megboyzz.dnevnik.util
|
||||||
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
@@ -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<Date> {
|
||||||
|
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date {
|
||||||
|
val date = json.asString
|
||||||
|
return Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Date> {
|
||||||
|
override fun serialize(date: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||||
|
val formattedDate = date?.time?.let { "/Date($it)/" }
|
||||||
|
return JsonPrimitive(formattedDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,4 +52,10 @@ class AuthorizationViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun acceptAlert(){
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
loginStatus.emit(AuthorizationStatus.NOTHING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
package ru.megboyzz.dnevnik.viewmodel
|
package ru.megboyzz.dnevnik.viewmodel
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.megboyzz.dnevnik.App
|
|
||||||
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
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.context.SchoolType
|
||||||
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
|
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
|
||||||
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
|
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ProfileViewModel @Inject constructor(
|
class ProfileViewModel @Inject constructor(
|
||||||
@@ -51,7 +47,7 @@ class ProfileViewModel @Inject constructor(
|
|||||||
lastName = info.lastName,
|
lastName = info.lastName,
|
||||||
groupName = personContext.group.name,
|
groupName = personContext.group.name,
|
||||||
term = term.number + 1,
|
term = term.number + 1,
|
||||||
isTerm = term.type == GroupType.Quarter,
|
isTerm = term.type == PeriodType.Quarter,
|
||||||
avatarUrl = info.avatarUrl
|
avatarUrl = info.avatarUrl
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package ru.megboyzz.dnevnik.viewmodel.subscreen
|
package ru.megboyzz.dnevnik.viewmodel.subscreen
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -7,26 +8,112 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||||
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
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.screens.subscreens.viewmodel.model.MarkModel
|
||||||
|
import ru.megboyzz.dnevnik.service.ContextService
|
||||||
|
import ru.megboyzz.dnevnik.service.MarksService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class LastMarksViewModel @Inject constructor(
|
class LastMarksViewModel @Inject constructor(
|
||||||
globalUserContextDao: GlobalUserContextDao,
|
globalUserContextDao: GlobalUserContextDao,
|
||||||
credentialsDao: CredentialsDao
|
credentialsDao: CredentialsDao,
|
||||||
|
marksService: MarksService,
|
||||||
|
contextService: ContextService
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
|
|
||||||
val lastMarksList = MutableStateFlow(listOf<MarkModel>())
|
val lastMarksList = MutableStateFlow(listOf<MarkModel>())
|
||||||
val isLoading = MutableStateFlow(true)
|
val isLoading = MutableStateFlow(true)
|
||||||
|
val isError = MutableStateFlow(false)
|
||||||
|
|
||||||
init{
|
init{
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
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<Teacher>
|
||||||
|
): List<MarkModel>{
|
||||||
|
|
||||||
|
val list = mutableListOf<MarkModel>()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
plugins {
|
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)
|
id("org.jetbrains.kotlin.android") version(Versions.kotlin) apply(false)
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ object AndroidTestLibraries: DependencyList() {
|
|||||||
|
|
||||||
//test libs
|
//test libs
|
||||||
val extJUnit = "androidx.test.ext:junit:${Versions.extJunit}"
|
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}"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ object Versions {
|
|||||||
//test
|
//test
|
||||||
val junit = "4.12"
|
val junit = "4.12"
|
||||||
val extJunit = "1.1.1"
|
val extJunit = "1.1.1"
|
||||||
val espresso = "3.2.0"
|
val espresso = "2.2.0"
|
||||||
|
|
||||||
val desugarJdkVersion = "1.1.5"
|
val desugarJdkVersion = "1.1.5"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -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"))
|
||||||
|
}
|
||||||
Vendored
+21
@@ -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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -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<Connection.Response> {
|
||||||
|
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 ""
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Map<String, String>>() {}.type
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromMap(cookies: Map<String, String>): String = gson.toJson(cookies)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toMap(savedCookies: String): Map<String, String> = gson.fromJson(savedCookies, mapType)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.megboyzz.dnevnik.db.converters
|
||||||
|
|
||||||
|
import androidx.room.TypeConverter
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
|
||||||
|
import ru.megboyzz.dnevnik.entities.response.UserContext
|
||||||
|
|
||||||
|
class DataStructureConverter {
|
||||||
|
|
||||||
|
val gson = Gson()
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromUserContext(userContext: UserContext): String = gson.toJson(userContext)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toUserContext(savedUserContext: String): UserContext = gson.fromJson(savedUserContext, UserContext::class.java)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun fromMarksContext(marksContext: MarksContext): String = gson.toJson(marksContext)
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
fun toMarksContext(savedMarksContext: String): MarksContext = gson.fromJson(savedMarksContext, MarksContext::class.java)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.megboyzz.dnevnik.db.dao
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import ru.megboyzz.dnevnik.entities.db.Credentials
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface CredentialsDao {
|
||||||
|
|
||||||
|
@Query("SELECT EXISTS(SELECT * FROM Credentials WHERE id = 0)")
|
||||||
|
fun credentialsIsExists(): Boolean
|
||||||
|
@Query("SELECT * FROM Credentials WHERE id = 0")
|
||||||
|
fun getCredentials(): Credentials
|
||||||
|
|
||||||
|
@Query("DELETE FROM Credentials WHERE id = 0")
|
||||||
|
fun deleteCredentials()
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun setCredentials(profile: Credentials)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package ru.megboyzz.dnevnik.db.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import ru.megboyzz.dnevnik.entities.db.GlobalUserContext
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface GlobalUserContextDao {
|
||||||
|
|
||||||
|
@Query("SELECT EXISTS(SELECT * FROM GlobalUserContext WHERE id = 0)")
|
||||||
|
fun globalUserContextIsExists(): Boolean
|
||||||
|
|
||||||
|
@Query("SELECT * FROM GlobalUserContext WHERE id = 0")
|
||||||
|
fun getGlobalUserContext(): GlobalUserContext
|
||||||
|
|
||||||
|
@Query("DELETE FROM GlobalUserContext WHERE id = 0")
|
||||||
|
fun deleteGlobalUserContext()
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun setGlobalUserContext(profile: GlobalUserContext)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package ru.megboyzz.dnevnik.di
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import dagger.BindsInstance
|
||||||
|
import dagger.Component
|
||||||
|
import ru.megboyzz.dnevnik.MainActivity
|
||||||
|
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
||||||
|
import ru.megboyzz.dnevnik.di.modules.AppModule
|
||||||
|
import ru.megboyzz.dnevnik.entities.db.Credentials
|
||||||
|
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||||
|
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
||||||
|
import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = [AppModule::class])
|
||||||
|
interface AppComponent {
|
||||||
|
|
||||||
|
val authViewModel: AuthorizationViewModel
|
||||||
|
|
||||||
|
val profileViewModel: ProfileViewModel
|
||||||
|
|
||||||
|
val lastMarksViewModel: LastMarksViewModel
|
||||||
|
|
||||||
|
val credentialsDao: CredentialsDao
|
||||||
|
|
||||||
|
val globalUserContextDao: GlobalUserContextDao
|
||||||
|
fun inject(viewModel: ProfileViewModel)
|
||||||
|
fun inject(activity: MainActivity)
|
||||||
|
|
||||||
|
@Component.Builder
|
||||||
|
interface Builder {
|
||||||
|
fun appModule(appModule: AppModule): Builder // add this
|
||||||
|
fun build(): AppComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package ru.megboyzz.dnevnik.di.modules
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import retrofit2.create
|
||||||
|
import ru.megboyzz.dnevnik.service.ContextService
|
||||||
|
import ru.megboyzz.dnevnik.service.MarksService
|
||||||
|
import ru.megboyzz.dnevnik.util.gson.DateDeserializer
|
||||||
|
import ru.megboyzz.dnevnik.util.gson.DateSerializer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class ApiModule {
|
||||||
|
|
||||||
|
private val apiUrl = "https://api.dnevnik.ru"
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideApiRetrofit(gson: Gson): Retrofit{
|
||||||
|
return Retrofit
|
||||||
|
.Builder()
|
||||||
|
.baseUrl(apiUrl)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideGson(): Gson = GsonBuilder()
|
||||||
|
//.registerTypeAdapter(Date::class.java, DateSerializer())
|
||||||
|
.registerTypeAdapter(Date::class.java, DateDeserializer())
|
||||||
|
.create()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideContextService(retrofit: Retrofit): ContextService = retrofit.create()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideMarksService(retrofit: Retrofit): MarksService = retrofit.create()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.megboyzz.dnevnik.di.modules
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module(includes = [ApiModule::class, DatabaseModule::class, AuthModule::class])
|
||||||
|
class AppModule(private val application: Application) {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideContext(): Context = this.application.applicationContext
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideApplication(): Application = application
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package ru.megboyzz.dnevnik.di.modules
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
||||||
|
import ru.megboyzz.dnevnik.service.ContextService
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class AuthModule {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ru.megboyzz.dnevnik.di.modules
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import ru.megboyzz.dnevnik.db.AppDataBase
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||||
|
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
||||||
|
import ru.megboyzz.dnevnik.entities.db.Credentials
|
||||||
|
import javax.inject.Singleton
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
@Module
|
||||||
|
class DatabaseModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideDatabase(context: Context): AppDataBase {
|
||||||
|
return Room
|
||||||
|
.databaseBuilder(context, AppDataBase::class.java, "database")
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideCredentialsDao(dataBase: AppDataBase): CredentialsDao = dataBase.credentialsDao()
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideGlobalUserContextDao(dataBase: AppDataBase): GlobalUserContextDao = dataBase.globalUserContextDao()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.megboyzz.dnevnik.service
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.*
|
||||||
|
import ru.megboyzz.dnevnik.entities.response.UserContext
|
||||||
|
import ru.megboyzz.dnevnik.entities.teachers.Teacher
|
||||||
|
|
||||||
|
interface ContextService{
|
||||||
|
|
||||||
|
@GET("/mobile/v4/users/{personId}/context")
|
||||||
|
fun getContext(@Header("Access-Token") accessToken: String, @Path("personId") personId: String): Call<UserContext>
|
||||||
|
|
||||||
|
@GET("/v1/edu-groups/{groupId}/teachers")
|
||||||
|
fun getTeachers(
|
||||||
|
@Header("Access-Token") accessToken: String,
|
||||||
|
@Path("groupId") groupId: Long
|
||||||
|
): Call<List<Teacher>>
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.megboyzz.dnevnik.service
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import ru.megboyzz.dnevnik.entities.marks.RecentMarks
|
||||||
|
|
||||||
|
//Сервис для последних оценок
|
||||||
|
interface MarksService {
|
||||||
|
|
||||||
|
@GET("/v2/persons/{personId}/group/{groupId}/recentmarks")
|
||||||
|
fun getRecentMarks(
|
||||||
|
@Header("Access-Token") accessToken: String,
|
||||||
|
@Path("personId") personId: Long,
|
||||||
|
@Path("groupId") groupId: Long
|
||||||
|
): Call<RecentMarks>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Date> {
|
||||||
|
override fun deserialize(json: JsonElement, type: Type, context: JsonDeserializationContext): Date {
|
||||||
|
val date = json.asString
|
||||||
|
return Date()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Date> {
|
||||||
|
override fun serialize(date: Date?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
|
||||||
|
val formattedDate = date?.time?.let { "/Date($it)/" }
|
||||||
|
return JsonPrimitive(formattedDate)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package ru.megboyzz.data
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
fun addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||||
|
plugins {
|
||||||
|
id("java-library")
|
||||||
|
alias(libs.plugins.org.jetbrains.kotlin.jvm)
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_7
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user