Compare commits
10
Commits
481cd770f4
...
7c876dc10b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c876dc10b | ||
|
|
655e8424e5 | ||
|
|
09800f9567 | ||
|
|
8b06ccdaa2 | ||
|
|
4029e93768 | ||
|
|
0ebc9f762b | ||
|
|
d04ceb323f | ||
|
|
1914b11bc9 | ||
|
|
dbc528b45d | ||
|
|
fe2acddc44 |
Generated
-3
@@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
Generated
-123
@@ -1,123 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
Generated
-5
@@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-17
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="SERIAL_NUMBER" />
|
||||
<value value="localhost:58526" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2023-04-02T15:04:59.965857200Z" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
-21
@@ -1,21 +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="gradleJvm" value="corretto-17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/buildSrc" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
-23
@@ -1,23 +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="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17_PREVIEW" 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>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" 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(("
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-6
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -31,11 +32,11 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "11"
|
||||
freeCompilerArgs = listOf(
|
||||
"-Xjvm-default=compatibility"
|
||||
)
|
||||
@@ -56,13 +57,35 @@ android {
|
||||
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
//std lib
|
||||
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
|
||||
|
||||
//app libs
|
||||
implementation(AppDependencies.appLibraries)
|
||||
implementation(AppLibraries)
|
||||
//test libs
|
||||
testImplementation(AppDependencies.testLibraries)
|
||||
androidTestImplementation(AppDependencies.androidTestLibraries)
|
||||
testImplementation(TestLibraries)
|
||||
androidTestImplementation(AndroidTestLibraries)
|
||||
|
||||
kapt(KaptLibraries)
|
||||
|
||||
coreLibraryDesugaring(DesugaringLibraries)
|
||||
debugImplementation(DebugLibraries)
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
android:roundIcon="@drawable/icon_rounded"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MyDnevnik"
|
||||
android:name=".App"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.megboyzz.dnevnik
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import ru.megboyzz.dnevnik.db.AppDataBase
|
||||
import ru.megboyzz.dnevnik.di.AppComponent
|
||||
import ru.megboyzz.dnevnik.di.DaggerAppComponent
|
||||
import ru.megboyzz.dnevnik.di.modules.AppModule
|
||||
|
||||
class App : Application() {
|
||||
|
||||
lateinit var appComponent: AppComponent
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
appComponent = DaggerAppComponent
|
||||
.builder()
|
||||
.appModule(AppModule(this))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Специальное расширение для получения AppComponent в любом месте, где у вас есть доступ к Context.
|
||||
* Позволяет избегать статического хранения ссылки на ваш [Application] класс
|
||||
*/
|
||||
val Context.appComponent: AppComponent
|
||||
get() = when (this) {
|
||||
is App -> appComponent
|
||||
else -> applicationContext.appComponent
|
||||
}
|
||||
@@ -1,21 +1,39 @@
|
||||
package ru.megboyzz.dnevnik
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.window.SplashScreen
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.ViewModel
|
||||
import ru.megboyzz.dnevnik.db.dao.CredentialsDao
|
||||
import ru.megboyzz.dnevnik.db.dao.GlobalUserContextDao
|
||||
import ru.megboyzz.dnevnik.navigation.AppNavHost
|
||||
import ru.megboyzz.dnevnik.ui.theme.*
|
||||
import ru.megboyzz.dnevnik.ui.theme.lightGray
|
||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||
import javax.inject.Inject
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
lateinit var app: App
|
||||
|
||||
@Inject
|
||||
lateinit var credentialsDao: CredentialsDao
|
||||
|
||||
@Inject
|
||||
lateinit var globalUserContext: GlobalUserContextDao
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
this.app = application as App
|
||||
app.appComponent.inject(this)
|
||||
|
||||
setContent {
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = lightGray
|
||||
@@ -24,4 +42,19 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
thread {
|
||||
if(credentialsDao.credentialsIsExists()) {
|
||||
val credentials = credentialsDao.getCredentials()
|
||||
if (!credentials.rememberMe) {
|
||||
credentialsDao.deleteCredentials()
|
||||
globalUserContext.deleteGlobalUserContext()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class GroupInfo(
|
||||
val id: Long,
|
||||
val name: String
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class PeriodGroup(
|
||||
val id: Long,
|
||||
val type: PeriodType, //Обычно равен "Quarter", то есть Четверть, но как будет семестр у них????
|
||||
val periods: List<PeriodInfo>
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class PeriodInfo(
|
||||
val id: Long,
|
||||
val number: Short,
|
||||
val type: PeriodType,
|
||||
val dateStart: Long,
|
||||
val dateFinish: Long,
|
||||
val studyYear: Short,
|
||||
val isCurrent: Boolean
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
enum class PeriodType {
|
||||
Quarter
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class PersonContext(
|
||||
val sex: String,
|
||||
val userId: Long,
|
||||
val personId: Long,
|
||||
val firstName: String,
|
||||
val middleName: String,
|
||||
val lastName: String,
|
||||
val avatarUrl: String,
|
||||
val school: SchoolInfo,
|
||||
val group: GroupInfo,
|
||||
val reportingPeriodGroup: PeriodGroup
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class SchoolInfo(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val avatarUrl: String,
|
||||
val type: SchoolType
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
enum class SchoolType {
|
||||
Additional,
|
||||
Regular
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.megboyzz.dnevnik.entities.context
|
||||
|
||||
data class UserInfo(
|
||||
val sex: String,
|
||||
val roles: List<String>,
|
||||
val userId: Long,
|
||||
val personId: Long,
|
||||
val firstName: String,
|
||||
val middleName: String,
|
||||
val lastName: String,
|
||||
val avatarUrl: String,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
data class Group(
|
||||
val groupId: String,
|
||||
val groupName: String,
|
||||
val reportingPeriodNotSet: Boolean,
|
||||
val periodTabName: String
|
||||
)
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
data class GroupHistorical(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val studyYear: Int,
|
||||
val periods: List<Period>,
|
||||
val periodTabName: String
|
||||
)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
data class MarksContext(
|
||||
val apiUrl: String,
|
||||
val context: UserContext
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
data class Period(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val shortName: String,
|
||||
val number: Int,
|
||||
val isDefault: Boolean
|
||||
)
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
data class SchoolMembership(
|
||||
val personId: String,
|
||||
val firstName: String,
|
||||
val lastName: String,
|
||||
val avatarUrl: String,
|
||||
val schoolId: String,
|
||||
val schoolName: String,
|
||||
val isOdo: Boolean,
|
||||
val isTermReportEnabled: Boolean,
|
||||
val studyYear: Int,
|
||||
val schoolType: SchoolType,
|
||||
val groups: List<Group>,
|
||||
val holidays: List<String>,
|
||||
val groupHistory: List<GroupHistorical>
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
enum class SchoolType {
|
||||
|
||||
Regular,
|
||||
Additional
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.megboyzz.dnevnik.entities.context.marks.init.state
|
||||
|
||||
|
||||
data class UserContext(
|
||||
val userId: String,
|
||||
val role: String,
|
||||
val schoolMemberships: List<SchoolMembership>,
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package ru.megboyzz.dnevnik.entities.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import ru.megboyzz.dnevnik.db.converters.CookiesConverter
|
||||
|
||||
@Entity
|
||||
@TypeConverters(CookiesConverter::class)
|
||||
data class Credentials(
|
||||
@PrimaryKey
|
||||
val id: Int = 0,
|
||||
val token: String,
|
||||
val cookies: Map<String, String>,
|
||||
val rememberMe: Boolean
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.megboyzz.dnevnik.entities.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
import ru.megboyzz.dnevnik.db.converters.DataStructureConverter
|
||||
import ru.megboyzz.dnevnik.entities.context.marks.init.state.MarksContext
|
||||
import ru.megboyzz.dnevnik.entities.response.UserContext
|
||||
|
||||
@Entity
|
||||
@TypeConverters(DataStructureConverter::class)
|
||||
data class GlobalUserContext(
|
||||
@PrimaryKey
|
||||
val id: Int = 0,
|
||||
val apiUserContext: UserContext, // Обычный контекст из API
|
||||
val marksInitStateUserContext: MarksContext //window.__MARKS__INITIAL__STATE__
|
||||
)
|
||||
@@ -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.response
|
||||
|
||||
data class TokenResponse(
|
||||
val login: String,
|
||||
val password: String
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.megboyzz.dnevnik.entities.response
|
||||
|
||||
import ru.megboyzz.dnevnik.entities.context.PersonContext
|
||||
import ru.megboyzz.dnevnik.entities.context.UserInfo
|
||||
|
||||
|
||||
data class UserContext(
|
||||
val info: UserInfo,
|
||||
val contextPersons: List<PersonContext>
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -1,22 +1,22 @@
|
||||
package ru.megboyzz.dnevnik.navigation
|
||||
|
||||
import android.window.SplashScreen
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.CubicBezierEasing
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.animation.core.tween
|
||||
import android.util.Log
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import ru.megboyzz.dnevnik.MainActivity
|
||||
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
||||
import ru.megboyzz.dnevnik.screens.*
|
||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||
import androidx.activity.viewModels
|
||||
import ru.megboyzz.dnevnik.appComponent
|
||||
import ru.megboyzz.dnevnik.util.collectAsMutableState
|
||||
import ru.megboyzz.dnevnik.util.navigate
|
||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||
|
||||
sealed class BaseNavRote(open val route: String)
|
||||
|
||||
@@ -31,18 +31,45 @@ sealed class AppNavRoute(override val route: String): BaseNavRote(route){
|
||||
object Settings: AppNavRoute("settings_screen")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun AppNavHost(){
|
||||
|
||||
Log.i("AppNavHost", "Called")
|
||||
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
val navController = rememberAnimatedNavController()
|
||||
val navController = rememberNavController()
|
||||
|
||||
val mainActivity = LocalContext.current as MainActivity
|
||||
|
||||
val authorizationViewModel: AuthorizationViewModel by mainActivity.viewModels{
|
||||
ViewModelFactory{
|
||||
Log.i("AuthorizationViewModel", "i am created!")
|
||||
mainActivity.appComponent.authViewModel
|
||||
}
|
||||
}
|
||||
|
||||
val loginStatus = authorizationViewModel.loginStatus.collectAsMutableState()
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = AppNavRoute.Login.route /* сделать проверку куки файлов */
|
||||
startDestination = AppNavRoute.Splash.route
|
||||
){
|
||||
composable(AppNavRoute.Splash.route) { }
|
||||
composable(AppNavRoute.Splash.route) {
|
||||
|
||||
when(loginStatus.value){
|
||||
|
||||
AuthorizationStatus.CREDENTIALS_LOADING -> { CircularProgressIndicator() }
|
||||
AuthorizationStatus.LOGGED -> {
|
||||
navController.navigate(AppNavRoute.Marks)
|
||||
}
|
||||
AuthorizationStatus.NOTHING -> {
|
||||
navController.navigate(AppNavRoute.Login)
|
||||
}
|
||||
else -> {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
composable(AppNavRoute.Login.route) { LoginScreen(navController) }
|
||||
composable(AppNavRoute.Marks.route) { MarksScreen(navController, scaffoldState) }
|
||||
composable(AppNavRoute.Schedule.route) { ScheduleScreen(navController, scaffoldState) }
|
||||
|
||||
@@ -7,17 +7,13 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavGraph
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavigatorProvider
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.createGraph
|
||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.screens.*
|
||||
import ru.megboyzz.dnevnik.screens.subscreens.LastMarksSubScreen
|
||||
import ru.megboyzz.dnevnik.screens.ui.SubScreenData
|
||||
import ru.megboyzz.dnevnik.screens.ui.SubScreenNavBar
|
||||
|
||||
@@ -31,11 +27,10 @@ sealed class MarksNavRoute(override val route: String): BaseNavRote(route){
|
||||
object ByYearMarks: MarksNavRoute("by_terms_marks")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun MarksSubScreenNavHost() {
|
||||
|
||||
val navController = rememberAnimatedNavController()
|
||||
val navController = rememberNavController()
|
||||
|
||||
val list = listOf(
|
||||
SubScreenData(
|
||||
@@ -82,7 +77,7 @@ fun MarksSubScreenNavHost() {
|
||||
navController = navController,
|
||||
startDestination = MarksNavRoute.LastMarks.route
|
||||
) {
|
||||
composable(MarksNavRoute.LastMarks.route) { Text(MarksNavRoute.LastMarks.route) }
|
||||
composable(MarksNavRoute.LastMarks.route) { LastMarksSubScreen() }
|
||||
composable(MarksNavRoute.ByDaysMarks.route) { Text(MarksNavRoute.ByDaysMarks.route) }
|
||||
composable(MarksNavRoute.StatsByMarks.route) { Text(MarksNavRoute.StatsByMarks.route) }
|
||||
composable(MarksNavRoute.StatsBySubjects.route) { Text(MarksNavRoute.StatsBySubjects.route) }
|
||||
|
||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||
|
||||
|
||||
@@ -1,36 +1,145 @@
|
||||
package ru.megboyzz.dnevnik.screens
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import android.util.Log
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.dnevnik.MainActivity
|
||||
import ru.megboyzz.dnevnik.appComponent
|
||||
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
||||
import ru.megboyzz.dnevnik.util.collectAsMutableState
|
||||
import ru.megboyzz.dnevnik.util.navigate
|
||||
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
||||
import ru.megboyzz.dnevnik.screens.ui.AlertMessageBox
|
||||
import ru.megboyzz.dnevnik.screens.ui.LoginScreenContent
|
||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||
|
||||
//TODO Переверстать экран входа
|
||||
@Composable
|
||||
fun LoginScreen(navController: NavController) {
|
||||
|
||||
val login = remember {
|
||||
mutableStateOf("")
|
||||
Log.i("LoginScreen", "Called")
|
||||
|
||||
val activity = LocalContext.current as MainActivity
|
||||
|
||||
val authorizationViewModel: AuthorizationViewModel by activity.viewModels {
|
||||
ViewModelFactory{
|
||||
activity.appComponent.authViewModel
|
||||
}
|
||||
}
|
||||
val password = remember {
|
||||
mutableStateOf("")
|
||||
|
||||
val login = authorizationViewModel.login.collectAsMutableState()
|
||||
|
||||
val password = authorizationViewModel.password.collectAsMutableState()
|
||||
|
||||
val loginStatus = authorizationViewModel.loginStatus.collectAsMutableState()
|
||||
|
||||
val rememberMe = authorizationViewModel.rememberMe.collectAsMutableState()
|
||||
|
||||
var isError by remember{
|
||||
mutableStateOf(true)
|
||||
}
|
||||
val isError = remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val rememberMe = remember {
|
||||
|
||||
var isLoading by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
LoginScreenContent(
|
||||
login = login,
|
||||
password = password,
|
||||
isError = isError,
|
||||
isRememberMe = rememberMe,
|
||||
onSignIn = { navController.navigate(AppNavRoute.Marks.route) },
|
||||
onSignUp = { /*TODO*/ },
|
||||
onForgetPassword = { /* TODO */ }
|
||||
)
|
||||
if(loginStatus.value == AuthorizationStatus.LOGGING) isLoading = true
|
||||
|
||||
if(loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS && isError) {
|
||||
AlertMessageBox(
|
||||
title = "Информация",
|
||||
alertText = "Неверный логин или пароль",
|
||||
onClick = { isError =! isError }
|
||||
)
|
||||
}
|
||||
|
||||
if(loginStatus.value == AuthorizationStatus.MAINTAIN){
|
||||
isLoading = false
|
||||
AlertMessageBox(
|
||||
title = "Ведутся профилактические работы",
|
||||
alertText = "Прямо сейчас мы проводим важные обновления, чтобы стать ещё лучше и удобнее. Пожалуйста, проявите немного терпения!",
|
||||
onClick = { authorizationViewModel.acceptAlert() }
|
||||
)
|
||||
}
|
||||
|
||||
if(loginStatus.value == AuthorizationStatus.NO_INTERNET){
|
||||
isLoading = false
|
||||
AlertMessageBox(
|
||||
title = "Информация",
|
||||
alertText = "Отсутствует подключение к сети",
|
||||
onClick = { loginStatus.value = AuthorizationStatus.NOTHING }
|
||||
)
|
||||
}
|
||||
|
||||
if(loginStatus.value == AuthorizationStatus.LOGGED) {
|
||||
Log.i("loginStatus", "logged")
|
||||
navController.navigate(AppNavRoute.Marks)
|
||||
}
|
||||
|
||||
Box(){
|
||||
LoginScreenContent(
|
||||
login = login.value,
|
||||
onLoginChange = { login.value = it },
|
||||
|
||||
password = password.value,
|
||||
onPasswordChange = { password.value = it },
|
||||
|
||||
isError = loginStatus.value == AuthorizationStatus.WRONG_CREDENTIALS,
|
||||
onErrorChange = { },
|
||||
|
||||
isLoading = isLoading,
|
||||
|
||||
isRememberMe = rememberMe.value,
|
||||
onRememberChange = { rememberMe.value = it },
|
||||
|
||||
onSignIn = {
|
||||
if (loginStatus.value != AuthorizationStatus.LOGGED)
|
||||
authorizationViewModel.login()
|
||||
},
|
||||
onSignUp = { /*TODO*/ },
|
||||
onForgetPassword = { /* TODO */ }
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)){
|
||||
Button(onClick = {
|
||||
login.value = "08012002@mail.ru"
|
||||
password.value = "1282565121024QWEasdfZXC"
|
||||
}) {
|
||||
Text("Я")
|
||||
}
|
||||
Button(onClick = {
|
||||
login.value = "kitsekaterina"
|
||||
password.value = "k12345"
|
||||
}) {
|
||||
Text("Катя")
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
var isRemoving by remember{ mutableStateOf(false) }
|
||||
Button(onClick = {
|
||||
val globalUserContextDao = activity.appComponent.globalUserContextDao
|
||||
val credentialsDao = activity.appComponent.credentialsDao
|
||||
scope.launch(Dispatchers.IO) {
|
||||
isRemoving = true
|
||||
globalUserContextDao.deleteGlobalUserContext()
|
||||
credentialsDao.deleteCredentials()
|
||||
isRemoving = false
|
||||
}
|
||||
}) {
|
||||
Text("Удалить данные входа")
|
||||
}
|
||||
if(isRemoving) CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.navigation.MarksSubScreenNavHost
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||
|
||||
@@ -3,8 +3,8 @@ package ru.megboyzz.dnevnik.screens
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.NavController
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.MainScaffold
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import ru.megboyzz.dnevnik.*
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
||||
import ru.megboyzz.dnevnik.screens.ui.UnderlinedText
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
import ru.megboyzz.dnevnik.util.*
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
@@ -23,9 +25,7 @@ fun SettingsScreen(
|
||||
) {
|
||||
|
||||
SettingsScaffold(
|
||||
onLeave = {
|
||||
//navController.navigate()
|
||||
},
|
||||
onLeave = { navController.navigate(AppNavRoute.Marks) },
|
||||
scaffoldState = scaffoldState,
|
||||
navController = navController
|
||||
) {
|
||||
|
||||
@@ -1,2 +1,9 @@
|
||||
package ru.megboyzz.dnevnik.screens.subscreens
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun ByDayMarksScreen() {
|
||||
|
||||
|
||||
}
|
||||
@@ -1,2 +1,57 @@
|
||||
package ru.megboyzz.dnevnik.screens.subscreens
|
||||
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import ru.megboyzz.dnevnik.MainActivity
|
||||
import ru.megboyzz.dnevnik.appComponent
|
||||
import ru.megboyzz.dnevnik.screens.ui.LastMarkCard
|
||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||
import ru.megboyzz.dnevnik.viewmodel.subscreen.LastMarksViewModel
|
||||
|
||||
@Composable
|
||||
fun LastMarksSubScreen() {
|
||||
|
||||
val activity = LocalContext.current as MainActivity
|
||||
|
||||
val lastMarksViewModel: LastMarksViewModel by activity.viewModels {
|
||||
ViewModelFactory{
|
||||
activity.appComponent.lastMarksViewModel
|
||||
}
|
||||
}
|
||||
|
||||
val isLoading = lastMarksViewModel.isLoading.collectAsState()
|
||||
|
||||
val lastMarks = lastMarksViewModel.lastMarksList.collectAsState()
|
||||
|
||||
if(isLoading.value){
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
){
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}else{
|
||||
|
||||
LazyColumn{
|
||||
|
||||
items(lastMarks.value){
|
||||
LastMarkCard(markModel = it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class MarkModel(
|
||||
val subjectName: String,
|
||||
val mark: String,
|
||||
val date: Date,
|
||||
val teacher: String,
|
||||
val reason: String = "null"
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.megboyzz.dnevnik.screens.ui
|
||||
|
||||
import android.app.AlertDialog
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.*
|
||||
@@ -8,6 +9,7 @@ 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.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
@@ -20,11 +22,13 @@ 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.draw.clipToBounds
|
||||
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.graphics.painter.Painter
|
||||
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
|
||||
@@ -33,10 +37,16 @@ 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.screens.subscreens.viewmodel.model.MarkModel
|
||||
import ru.megboyzz.dnevnik.ui.theme.*
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.util.SpacerHeight
|
||||
import ru.megboyzz.dnevnik.util.SpacerWidth
|
||||
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
|
||||
|
||||
|
||||
//Рефакторинг компонентов: разбиение файла с компонентами на отдельные файлы
|
||||
@@ -74,25 +84,29 @@ fun AlmostOutlinedText(
|
||||
@Composable
|
||||
fun ProfileCard(
|
||||
textColor: Color = white,
|
||||
painter: Painter
|
||||
userProfileModel: UserProfileModel
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.padding(10.dp, 15.dp, 20.dp, 15.dp),
|
||||
painter = painter, //TODO заменить динмической картинкой
|
||||
contentDescription = "chel"
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.padding(10.dp, 15.dp, 20.dp, 15.dp)
|
||||
.clip(RoundedCornerShape(25.dp))
|
||||
.size(50.dp),
|
||||
contentScale = ContentScale.Crop,
|
||||
model = userProfileModel.avatarUrl,
|
||||
contentDescription = ""
|
||||
)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(5.dp)) {
|
||||
Text(
|
||||
text = "Имя и фамилия",
|
||||
text = userProfileModel.toString(),
|
||||
style = H1,
|
||||
color = textColor
|
||||
)
|
||||
Text(
|
||||
text = "Класс/Группа",
|
||||
text = userProfileModel.groupName,
|
||||
style = H1,
|
||||
color = textColor
|
||||
)
|
||||
@@ -102,18 +116,20 @@ fun ProfileCard(
|
||||
|
||||
@Composable
|
||||
fun MainTextField(
|
||||
value: MutableState<String>,
|
||||
isError: MutableState<Boolean> = remember { mutableStateOf(false) },
|
||||
value: String,
|
||||
onChange: (String) -> Unit,
|
||||
isError: Boolean,
|
||||
isLoading: Boolean = false,
|
||||
onErrorChanged: (Boolean) -> Unit,
|
||||
label: String = "",
|
||||
keyboardType: KeyboardType = KeyboardType.Text,
|
||||
onChange: (it: String) -> Unit = { value.value = it }
|
||||
) {
|
||||
|
||||
var passwordVisible by rememberSaveable { mutableStateOf(false) }
|
||||
val focusRequester = FocusRequester()
|
||||
|
||||
OutlinedTextField(
|
||||
value = value.value,
|
||||
value = value,
|
||||
onValueChange = onChange,
|
||||
label = {
|
||||
Text(
|
||||
@@ -126,8 +142,8 @@ fun MainTextField(
|
||||
textStyle = H2,
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(
|
||||
textColor = dark,
|
||||
unfocusedBorderColor = if (isError.value) falseColor else mainBlue,
|
||||
focusedBorderColor = if (isError.value) falseColor else mainBlue,
|
||||
unfocusedBorderColor = if (isError) falseColor else mainBlue,
|
||||
focusedBorderColor = if (isError) falseColor else mainBlue,
|
||||
focusedLabelColor = dark,
|
||||
unfocusedLabelColor = dark,
|
||||
cursorColor = dark
|
||||
@@ -152,42 +168,50 @@ fun MainTextField(
|
||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||
Icon(imageVector = image, "")
|
||||
}
|
||||
} else {
|
||||
if(isLoading) LoginProgress()
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged {
|
||||
if (it.isFocused)
|
||||
if (isError.value) isError.value = false
|
||||
if (isError) onErrorChanged(false)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoginProgress() {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
strokeWidth = 2.dp,
|
||||
color = mainBlue
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainCheckbox(
|
||||
isChecked: MutableState<Boolean>,
|
||||
isChecked: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
size: Float = 24f,
|
||||
checkedColor: Color = mainBlue,
|
||||
uncheckedColor: Color = lightGray,
|
||||
checkmarkColor: Color = white,
|
||||
onValueChange: () -> Unit
|
||||
onValueChange: (Boolean) -> Unit
|
||||
) {
|
||||
|
||||
val checkboxColor: Color by animateColorAsState(if (isChecked.value) checkedColor else uncheckedColor)
|
||||
val checkboxColor: Color by animateColorAsState(if (isChecked) checkedColor else uncheckedColor)
|
||||
val density = LocalDensity.current
|
||||
val duration = 200
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.toggleable(
|
||||
value = isChecked.value,
|
||||
value = isChecked,
|
||||
role = Role.Checkbox,
|
||||
onValueChange = {
|
||||
isChecked.value = !isChecked.value
|
||||
onValueChange.invoke()
|
||||
}
|
||||
onValueChange = onValueChange
|
||||
)
|
||||
) {
|
||||
Card(
|
||||
@@ -202,7 +226,7 @@ fun MainCheckbox(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
visible = isChecked.value,
|
||||
visible = isChecked,
|
||||
enter = slideInHorizontally(
|
||||
animationSpec = tween(duration)
|
||||
) {
|
||||
@@ -345,11 +369,7 @@ fun CardWithAverage(
|
||||
|
||||
@Composable
|
||||
fun LastMarkCard(
|
||||
subjectName: String,
|
||||
mark: Int,
|
||||
date: String, /* мейби стоит поменять на Date или инстанс */
|
||||
teacher: String,
|
||||
reason: String = "null"
|
||||
markModel: MarkModel
|
||||
) {
|
||||
BaseCard {
|
||||
Row(
|
||||
@@ -362,29 +382,29 @@ fun LastMarkCard(
|
||||
modifier = Modifier.width(200.dp)
|
||||
) {
|
||||
Text(
|
||||
text = subjectName,
|
||||
text = markModel.subjectName,
|
||||
style = MainText
|
||||
)
|
||||
Text(
|
||||
text = mark.toString(),
|
||||
text = markModel.mark.toString(),
|
||||
style = H1,
|
||||
color = mainBlue
|
||||
)
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Text(
|
||||
text = "${R.string.title_mark_recive.AsString()} $date",
|
||||
text = "${R.string.title_mark_recive.AsString()} ${markModel.date}",
|
||||
style = MainText,
|
||||
color = dark
|
||||
)
|
||||
Text(
|
||||
text = "${R.string.title_teacher_name.AsString()}: $teacher",
|
||||
text = "${R.string.title_teacher_name.AsString()}: ${markModel.teacher}",
|
||||
style = MainText,
|
||||
color = dark
|
||||
)
|
||||
if(reason != "null")
|
||||
if(markModel.reason != "null")
|
||||
Text(
|
||||
text = "${R.string.title_reason.AsString()} $reason",
|
||||
text = "${R.string.title_reason.AsString()} ${markModel.reason}",
|
||||
style = MainText,
|
||||
color = dark
|
||||
)
|
||||
@@ -618,7 +638,8 @@ fun AlertMessageBox(
|
||||
contentColor = white
|
||||
),
|
||||
modifier = Modifier
|
||||
.defaultMinSize(170.dp).padding(bottom = 30.dp)
|
||||
.defaultMinSize(170.dp)
|
||||
.padding(bottom = 30.dp)
|
||||
) {
|
||||
Text(
|
||||
text = R.string.title_ok.AsString(),
|
||||
@@ -630,3 +651,65 @@ fun AlertMessageBox(
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@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(
|
||||
backgroundColor = mainBlue,
|
||||
contentColor = white
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(bottom = 30.dp)
|
||||
){
|
||||
Text(
|
||||
text = title,
|
||||
style = H1
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||
|
||||
@@ -19,13 +19,22 @@ import ru.megboyzz.dnevnik.ui.theme.MainText
|
||||
import ru.megboyzz.dnevnik.ui.theme.Mini
|
||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.util.SpacerHeight
|
||||
import ru.megboyzz.dnevnik.util.SpacerWidth
|
||||
|
||||
@Composable
|
||||
fun LoginScreenContent(
|
||||
login: MutableState<String>,
|
||||
password: MutableState<String>,
|
||||
isError: MutableState<Boolean>,
|
||||
isRememberMe: MutableState<Boolean>,
|
||||
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
|
||||
@@ -65,14 +74,19 @@ fun LoginScreenContent(
|
||||
MainTextField(
|
||||
value = login,
|
||||
label = R.string.title_login.AsString(),
|
||||
isError = isError
|
||||
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
|
||||
isError = isError,
|
||||
onErrorChanged = onErrorChange,
|
||||
onChange = onPasswordChange
|
||||
)
|
||||
}
|
||||
SpacerHeight(5.dp)
|
||||
@@ -87,7 +101,10 @@ fun LoginScreenContent(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
MainCheckbox(isRememberMe) { }
|
||||
MainCheckbox(
|
||||
isChecked = isRememberMe,
|
||||
onValueChange = onRememberChange
|
||||
)
|
||||
SpacerWidth(5.dp)
|
||||
Text(
|
||||
text = R.string.title_remember_me.AsString(),
|
||||
|
||||
@@ -19,11 +19,14 @@ 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.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||
import com.kizitonwose.calendar.core.daysOfWeek
|
||||
import ru.megboyzz.dnevnik.util.navigate
|
||||
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
||||
import ru.megboyzz.dnevnik.screens.SettingsScreen
|
||||
import ru.megboyzz.dnevnik.screens.subscreens.viewmodel.model.MarkModel
|
||||
import ru.megboyzz.dnevnik.screens.ui.*
|
||||
import ru.megboyzz.dnevnik.screens.ui.calendar.NiceCalendar
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.DrawerContent
|
||||
import ru.megboyzz.dnevnik.screens.ui.main.SettingsScaffold
|
||||
import ru.megboyzz.dnevnik.screens.ui.piechart.PieChart
|
||||
import ru.megboyzz.dnevnik.screens.ui.piechart.PieChartCard
|
||||
@@ -68,7 +71,7 @@ fun text() {
|
||||
@Composable
|
||||
fun DrawerPrev() {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
val navController = rememberAnimatedNavController()
|
||||
val navController = rememberNavController()
|
||||
Box(Modifier.background(mainBlue)) {
|
||||
DrawerContent(navController = navController, scaffoldState = scaffoldState)
|
||||
}
|
||||
@@ -169,17 +172,6 @@ fun PieChartCardPrev() {
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun LastMarkCardPrev() {
|
||||
LastMarkCard(
|
||||
subjectName = "Русский язык",
|
||||
mark = 5,
|
||||
date = "18.02.2023",
|
||||
teacher = "Степанова А.С.",
|
||||
reason = "контрольная"
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -364,7 +356,7 @@ fun ScaffoldTest() {
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
val navController = rememberNavController()
|
||||
SettingsScaffold(
|
||||
onLeave = { /*TODO*/ },
|
||||
onLeave = { navController.navigate(AppNavRoute.Marks) },
|
||||
scaffoldState = scaffoldState,
|
||||
navController = navController
|
||||
) {
|
||||
@@ -391,3 +383,17 @@ fun Test1() {
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AlertTest() {
|
||||
|
||||
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.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import ru.megboyzz.dnevnik.navigate
|
||||
import ru.megboyzz.dnevnik.util.navigate
|
||||
import ru.megboyzz.dnevnik.navigation.BaseNavRote
|
||||
import ru.megboyzz.dnevnik.ui.theme.MainText
|
||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||
@@ -27,7 +27,7 @@ import ru.megboyzz.dnevnik.ui.theme.white
|
||||
fun SubScreenNavButton(
|
||||
text: String,
|
||||
isClicked: MutableState<Boolean>,
|
||||
onCLick: () -> Unit
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = if(isClicked.value) mainBlue else white,
|
||||
@@ -36,7 +36,7 @@ fun SubScreenNavButton(
|
||||
Button(
|
||||
onClick = {
|
||||
isClicked.value = !isClicked.value
|
||||
onCLick.invoke()
|
||||
onClick.invoke()
|
||||
},
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
border = BorderStroke(
|
||||
@@ -80,8 +80,9 @@ fun SubScreenNavBar(
|
||||
isClicked = data.isClicked
|
||||
) {
|
||||
for(d in list){
|
||||
if(data != d)
|
||||
if(data != d) {
|
||||
d.isClicked.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.dnevnik.*
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.ui.theme.*
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.util.rememberFirstMostVisibleMonth
|
||||
import ru.megboyzz.dnevnik.util.week
|
||||
import java.time.DayOfWeek
|
||||
import java.time.LocalDate
|
||||
import java.time.Month
|
||||
|
||||
@@ -20,6 +20,7 @@ import ru.megboyzz.dnevnik.ui.theme.MainText
|
||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||
import ru.megboyzz.dnevnik.ui.theme.white
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.util.*
|
||||
|
||||
@Composable
|
||||
fun BottomBar(navController: NavController) {
|
||||
|
||||
@@ -1,130 +1,203 @@
|
||||
package ru.megboyzz.dnevnik.screens.ui
|
||||
package ru.megboyzz.dnevnik.screens.ui.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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.material.Divider
|
||||
import androidx.compose.material.ScaffoldState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.RoundRect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.screens.ui.AlmostOutlinedText
|
||||
import ru.megboyzz.dnevnik.screens.ui.LeaveAlert
|
||||
import ru.megboyzz.dnevnik.screens.ui.ProfileCard
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||
import ru.megboyzz.dnevnik.ui.theme.white
|
||||
import ru.megboyzz.dnevnik.util.*
|
||||
import ru.megboyzz.dnevnik.viewmodel.AuthorizationViewModel
|
||||
import ru.megboyzz.dnevnik.viewmodel.ProfileViewModel
|
||||
import ru.megboyzz.dnevnik.viewmodel.core.ViewModelFactory
|
||||
|
||||
|
||||
@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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CoroutineCreationDuringComposition")
|
||||
@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()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(260.dp)
|
||||
.fillMaxSize()
|
||||
.padding(10.dp),
|
||||
horizontalAlignment = Alignment.Start
|
||||
){
|
||||
SpacerHeight(10.dp)
|
||||
ProfileCard(painter = R.drawable.ic_author.AsPainter())
|
||||
SpacerHeight(15.dp)
|
||||
AlmostOutlinedText(text = "Идет 3-я четверть")
|
||||
SpacerHeight(20.dp)
|
||||
|
||||
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(
|
||||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
modifier = Modifier.padding(5.dp, 0.dp)
|
||||
modifier = Modifier
|
||||
.width(260.dp)
|
||||
.fillMaxSize()
|
||||
.padding(10.dp)
|
||||
.background(mainBlue),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
DrawerMainButton(
|
||||
icon = R.drawable.ic_marks.AsPainter(),
|
||||
text = R.string.title_marks.AsString(),
|
||||
onClick = {
|
||||
navController.navigate(AppNavRoute.Marks)
|
||||
scope.launch { scaffoldState.drawerState.close() }
|
||||
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
|
||||
}
|
||||
)
|
||||
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()
|
||||
) { /* TODO */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val drawerShape = object : Shape {
|
||||
override fun createOutline(
|
||||
size: Size,
|
||||
layoutDirection: LayoutDirection,
|
||||
density: Density
|
||||
): Outline.Rounded {
|
||||
|
||||
val radius = CornerRadius(30f, 30f)
|
||||
val nullRadius = CornerRadius.Zero
|
||||
|
||||
return Outline.Rounded(
|
||||
RoundRect(
|
||||
left = 0f,
|
||||
top = 0f,
|
||||
right = size.width * 26 / 36,
|
||||
bottom = size.height,
|
||||
topLeftCornerRadius = nullRadius,
|
||||
topRightCornerRadius = radius,
|
||||
bottomRightCornerRadius = radius,
|
||||
bottomLeftCornerRadius = nullRadius
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawerMainButton(
|
||||
icon: Painter,
|
||||
@@ -132,26 +205,23 @@ fun DrawerMainButton(
|
||||
textColor: Color = white,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(30.dp)
|
||||
.mainClickable(onClick = onClick),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start
|
||||
){
|
||||
Image(
|
||||
painter = icon,
|
||||
contentDescription = "icon"
|
||||
)
|
||||
SpacerWidth(13.dp)
|
||||
Text(
|
||||
text = text,
|
||||
style = H2,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
.mainClickable { onClick.invoke() }
|
||||
){
|
||||
Image(
|
||||
painter = icon,
|
||||
contentDescription = "icon",
|
||||
Modifier.padding(start = 10.dp)
|
||||
)
|
||||
SpacerWidth(13.dp)
|
||||
Text(
|
||||
text = text,
|
||||
style = H2,
|
||||
color = textColor
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package ru.megboyzz.dnevnik.screens.ui.main
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
@@ -19,23 +16,21 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.SpacerWidth
|
||||
import ru.megboyzz.dnevnik.screens.ui.DrawerContent
|
||||
import ru.megboyzz.dnevnik.screens.ui.drawerShape
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.util.SpacerWidth
|
||||
import ru.megboyzz.dnevnik.ui.theme.mainBlue
|
||||
import ru.megboyzz.dnevnik.ui.theme.white
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.viewmodel.model.UserProfileModel
|
||||
|
||||
@Composable
|
||||
fun MainScaffold(
|
||||
icon: Painter,
|
||||
title: String,
|
||||
/* TODO viewModel */
|
||||
navController: NavController,
|
||||
scaffoldState: ScaffoldState,
|
||||
content: (@Composable (it: PaddingValues) -> Unit),
|
||||
content: @Composable (PaddingValues) -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -55,6 +50,9 @@ fun MainScaffold(
|
||||
navController = navController,
|
||||
scaffoldState = scaffoldState,
|
||||
bottomBar = { BottomBar(navController) },
|
||||
drawerContent = {
|
||||
DrawerContent(navController, scaffoldState)
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -68,6 +66,7 @@ fun BaseScaffold(
|
||||
navController: NavController,
|
||||
scaffoldState: ScaffoldState,
|
||||
bottomBar: @Composable () -> Unit,
|
||||
drawerContent: @Composable (ColumnScope) -> Unit = {},
|
||||
content: (@Composable (it: PaddingValues) -> Unit),
|
||||
) {
|
||||
Scaffold(
|
||||
@@ -99,10 +98,11 @@ fun BaseScaffold(
|
||||
}
|
||||
},
|
||||
content = content,
|
||||
drawerBackgroundColor = mainBlue,
|
||||
drawerContentColor = white,
|
||||
drawerShape = drawerShape,
|
||||
drawerContent = { DrawerContent(navController, scaffoldState) },
|
||||
drawerBackgroundColor = Color.Transparent,
|
||||
drawerContentColor = Color.Transparent,
|
||||
//drawerShape = drawerShape,
|
||||
drawerElevation = 0.dp,
|
||||
drawerContent = drawerContent,
|
||||
bottomBar = bottomBar
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package ru.megboyzz.dnevnik.screens.ui.piechart
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||
import ru.megboyzz.dnevnik.R
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
|
||||
@@ -8,8 +8,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ru.megboyzz.dnevnik.AsPainter
|
||||
import ru.megboyzz.dnevnik.AsString
|
||||
import ru.megboyzz.dnevnik.util.AsPainter
|
||||
import ru.megboyzz.dnevnik.util.AsString
|
||||
import ru.megboyzz.dnevnik.R
|
||||
import ru.megboyzz.dnevnik.screens.ui.BaseCard
|
||||
import ru.megboyzz.dnevnik.ui.theme.H2
|
||||
|
||||
@@ -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,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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+24
-12
@@ -1,4 +1,4 @@
|
||||
package ru.megboyzz.dnevnik
|
||||
package ru.megboyzz.dnevnik.util
|
||||
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -28,12 +28,12 @@ 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 kotlinx.coroutines.launch
|
||||
import ru.megboyzz.dnevnik.navigation.AppNavRoute
|
||||
import ru.megboyzz.dnevnik.navigation.BaseNavRote
|
||||
import ru.megboyzz.dnevnik.ui.theme.dark
|
||||
import java.time.DayOfWeek
|
||||
import kotlin.coroutines.*
|
||||
|
||||
@Composable
|
||||
fun Int.AsPainter() = painterResource(this)
|
||||
@@ -203,16 +203,28 @@ fun CalendarDay.week(): Week{
|
||||
|
||||
}
|
||||
|
||||
fun WeekDay.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,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,61 @@
|
||||
package ru.megboyzz.dnevnik.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.megboyzz.dnevnik.App
|
||||
import ru.megboyzz.dnevnik.authorization.AuthorizationManager
|
||||
import ru.megboyzz.dnevnik.authorization.AuthorizationStatus
|
||||
import java.lang.IllegalArgumentException
|
||||
import javax.inject.Inject
|
||||
|
||||
class AuthorizationViewModel @Inject constructor(
|
||||
private val authManager: AuthorizationManager
|
||||
): ViewModel() {
|
||||
|
||||
val login = MutableStateFlow("")
|
||||
val password = MutableStateFlow("")
|
||||
val rememberMe = MutableStateFlow(false)
|
||||
|
||||
val loginStatus = MutableStateFlow(AuthorizationStatus.CREDENTIALS_LOADING)
|
||||
|
||||
init {
|
||||
Log.i("AuthorizationViewModel", "AuthorizationViewModel in constructor")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
loginStatus.emit(authManager.checkCredentials())
|
||||
}
|
||||
}
|
||||
|
||||
fun login(){
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
loginStatus.emit(AuthorizationStatus.LOGGING)
|
||||
val login1 = authManager.login(login.value, password.value, rememberMe.value)
|
||||
Log.i("Auth", login1.toString())
|
||||
loginStatus.emit(login1)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun resetAuth(){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
loginStatus.emit(AuthorizationStatus.LOGOUT)
|
||||
login.emit("")
|
||||
password.emit("")
|
||||
}
|
||||
}
|
||||
|
||||
fun acceptAlert(){
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
loginStatus.emit(AuthorizationStatus.NOTHING)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user