Compare commits
31 Commits
master
..
315e7e4afe
| Author | SHA1 | Date | |
|---|---|---|---|
|
315e7e4afe
|
|||
|
55ff6e6cd1
|
|||
|
5e15a75664
|
|||
|
d7a2a26097
|
|||
|
f1b330c1ce
|
|||
|
1ede1a4c2d
|
|||
|
08949c5f0d
|
|||
|
be230e8a11
|
|||
| c63ca8c1da | |||
| 4c1a9c8765 | |||
| 5b6660aa8d | |||
| 46987c6fa5 | |||
| 3138afb0de | |||
| 455a3cae4c | |||
| 172f997af6 | |||
| dd88d4a81a | |||
| 8b57881e28 | |||
| 9e1e64f3fc | |||
| 83759f5a37 | |||
| da7498bf02 | |||
| a417d74154 | |||
| c09947865d | |||
| d4bb804f24 | |||
| fd49b30efb | |||
| 6e85ca4bdc | |||
| 3cf7885d9e | |||
| 0d4ba49111 | |||
| 6efb5998ae | |||
| 284e841e6c | |||
| 524bab9d6e | |||
| 983249091a |
@@ -1,12 +1,7 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea/
|
||||||
/.idea/libraries
|
|
||||||
/.idea/modules.xml
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/navEditor.xml
|
|
||||||
/.idea/assetWizardSettings.xml
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
|
||||||
<state>
|
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
|
||||||
</state>
|
|
||||||
</component>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="CompilerConfiguration">
|
|
||||||
<bytecodeTargetLevel target="17" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetDropDown">
|
|
||||||
<value>
|
|
||||||
<entry key="GreetingPreview">
|
|
||||||
<State />
|
|
||||||
</entry>
|
|
||||||
<entry key="GreetingPreview2">
|
|
||||||
<State />
|
|
||||||
</entry>
|
|
||||||
<entry key="LoginActivity">
|
|
||||||
<State />
|
|
||||||
</entry>
|
|
||||||
<entry key="app">
|
|
||||||
<State />
|
|
||||||
</entry>
|
|
||||||
</value>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,19 +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="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/app" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="KotlinJpsPluginSettings">
|
|
||||||
<option name="version" value="1.9.0" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectMigrations">
|
|
||||||
<option name="MigrateToGradleLocalJavaHome">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,6 +1,15 @@
|
|||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.androidApplication)
|
alias(libs.plugins.androidApplication)
|
||||||
alias(libs.plugins.jetbrainsKotlinAndroid)
|
alias(libs.plugins.jetbrainsKotlinAndroid)
|
||||||
|
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
|
||||||
|
id("com.google.devtools.ksp")
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets {
|
||||||
|
propertiesFileName = "secrets.properties"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -13,6 +22,7 @@ android {
|
|||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
setProperty("archivesBaseName", "$applicationId-v$versionCode($versionName)")
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@@ -24,6 +34,19 @@ android {
|
|||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
resValue("string", "app_name", "@string/app_name_reg")
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
applicationIdSuffix = ".debug"
|
||||||
|
versionNameSuffix =
|
||||||
|
"-debug+${LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE)}"
|
||||||
|
resValue("string", "app_name", "@string/app_name_dev")
|
||||||
|
}
|
||||||
|
create("beta") {
|
||||||
|
versionNameSuffix =
|
||||||
|
"-beta+${LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE)}"
|
||||||
|
resValue("string", "app_name", "@string/app_name_beta")
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -36,6 +59,7 @@ android {
|
|||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.5.1"
|
kotlinCompilerExtensionVersion = "1.5.1"
|
||||||
@@ -48,9 +72,10 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.5.0")
|
coreLibraryDesugaring(libs.desugar.jdk.libs)
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.core.splashscreen)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
@@ -71,16 +96,25 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|
||||||
implementation(libs.ktor.client.core)
|
implementation(libs.ktor.client.core)
|
||||||
implementation(libs.ktor.client.cio)
|
implementation(libs.ktor.client.cio)
|
||||||
implementation(libs.ktor.client.logging)
|
implementation(libs.ktor.client.logging)
|
||||||
|
implementation(libs.ktor.client.android)
|
||||||
|
implementation(libs.coil.compose)
|
||||||
|
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
implementation("com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl:3.0.0")
|
implementation(libs.splitties.base)
|
||||||
|
implementation(libs.splitties.room)
|
||||||
|
|
||||||
implementation("com.kizitonwose.calendar:compose:2.5.0")
|
implementation(libs.compose)
|
||||||
|
|
||||||
|
implementation(libs.kefirbb)
|
||||||
|
|
||||||
|
implementation(libs.acra.http)
|
||||||
|
|
||||||
|
implementation(libs.androidx.room.runtime)
|
||||||
|
implementation(libs.androidx.room.ktx)
|
||||||
|
ksp(libs.androidx.room.compiler)
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,10 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".UNNApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.UNN"
|
android:theme="@style/Theme.UNN"
|
||||||
@@ -15,6 +16,10 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.layout.MainActivity"
|
android:name=".ui.layout.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.UNN" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.layout.LoginActivity"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.UNN">
|
android:theme="@style/Theme.UNN">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@@ -22,10 +27,6 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
|
||||||
android:name=".ui.layout.LoginActivity"
|
|
||||||
android:exported="true"
|
|
||||||
android:theme="@style/Theme.UNN"/>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,392 @@
|
|||||||
|
package ru.sweetbread.unn
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.ktor.client.request.forms.submitForm
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.header
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import io.ktor.http.parameters
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import ru.sweetbread.unn.db.cacheSchedule
|
||||||
|
import ru.sweetbread.unn.db.cacheUser
|
||||||
|
import ru.sweetbread.unn.db.loadSchedule
|
||||||
|
import ru.sweetbread.unn.db.loadUserByBitrixId
|
||||||
|
import ru.sweetbread.unn.ui.layout.LoginData
|
||||||
|
import ru.sweetbread.unn.ui.layout.client
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
|
||||||
|
private lateinit var PHPSESSID: String
|
||||||
|
private lateinit var CSRF: String
|
||||||
|
lateinit var ME: User
|
||||||
|
|
||||||
|
const val portalURL = "https://portal.unn.ru"
|
||||||
|
const val ruzapiURL = "$portalURL/ruzapi"
|
||||||
|
const val vuzapiURL = "$portalURL/bitrix/vuz/api"
|
||||||
|
const val prtl2URL = "$portalURL/portal2/api"
|
||||||
|
const val restURL = "$portalURL/rest"
|
||||||
|
|
||||||
|
|
||||||
|
enum class Type(val s: String) {
|
||||||
|
Student("student"),
|
||||||
|
Group("group"),
|
||||||
|
Lecturer("lecturer"),
|
||||||
|
Auditorium("auditorium"),
|
||||||
|
Employee("employee")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class LecturerRank(val id: Int) {
|
||||||
|
Assistant(R.string.assistant),
|
||||||
|
Lecturer(R.string.lecturer),
|
||||||
|
SLecturer(R.string.slecturer),
|
||||||
|
AProfessor(R.string.aprofessor)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScheduleUnit(
|
||||||
|
val oid: Int,
|
||||||
|
val auditorium: Auditorium,
|
||||||
|
val date: LocalDate,
|
||||||
|
val discipline: Discipline,
|
||||||
|
val kindOfWork: KindOfWork,
|
||||||
|
val lecturers: ArrayList<Lecturer>,
|
||||||
|
val stream: String,
|
||||||
|
val begin: LocalTime,
|
||||||
|
val end: LocalTime
|
||||||
|
)
|
||||||
|
|
||||||
|
class Auditorium(
|
||||||
|
val name: String,
|
||||||
|
val oid: Int,
|
||||||
|
val floor: Int,
|
||||||
|
val building: Building
|
||||||
|
)
|
||||||
|
|
||||||
|
class Building(
|
||||||
|
val name: String,
|
||||||
|
val gid: Int,
|
||||||
|
val oid: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
class Discipline(
|
||||||
|
val name: String,
|
||||||
|
val oid: Int,
|
||||||
|
val type: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
class KindOfWork(
|
||||||
|
val name: String,
|
||||||
|
val oid: Int,
|
||||||
|
val uid: String,
|
||||||
|
val complexity: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
class Lecturer(
|
||||||
|
val name: String,
|
||||||
|
val rank: LecturerRank,
|
||||||
|
val email: String,
|
||||||
|
val unnId: Int,
|
||||||
|
val uid: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class User(
|
||||||
|
val unnId: Int?,
|
||||||
|
val bitrixId: Int,
|
||||||
|
val userId: Int,
|
||||||
|
val type: Type,
|
||||||
|
val email: String,
|
||||||
|
val nameRu: String,
|
||||||
|
val nameEn: String,
|
||||||
|
val isMale: Boolean,
|
||||||
|
val birthday: LocalDate,
|
||||||
|
val avatar: AvatarSet
|
||||||
|
)
|
||||||
|
|
||||||
|
class Post(
|
||||||
|
val id: Int,
|
||||||
|
val authorId: Int,
|
||||||
|
val enableComments: Boolean,
|
||||||
|
val numComments: Int,
|
||||||
|
val date: LocalDateTime,
|
||||||
|
val content: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class AvatarSet(
|
||||||
|
val original: String,
|
||||||
|
val thumbnail: String,
|
||||||
|
val small: String
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorize user by [login] and [password]
|
||||||
|
*
|
||||||
|
* Also defines local vars [PHPSESSID] and [ME]
|
||||||
|
*/
|
||||||
|
suspend fun auth(
|
||||||
|
login: String = LoginData.login,
|
||||||
|
password: String = LoginData.password,
|
||||||
|
forced: Boolean = false
|
||||||
|
): Boolean {
|
||||||
|
if (!forced) {
|
||||||
|
if (::PHPSESSID.isInitialized and ::ME.isInitialized)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val r = client.submitForm("$portalURL/auth/?login=yes",
|
||||||
|
formParameters = parameters {
|
||||||
|
append("AUTH_FORM", "Y")
|
||||||
|
append("TYPE", "AUTH")
|
||||||
|
append("backurl", "/")
|
||||||
|
append("USER_LOGIN", login)
|
||||||
|
append("USER_PASSWORD", password)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (r.status.value == 302) {
|
||||||
|
PHPSESSID =
|
||||||
|
"""PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
|
||||||
|
getMyself(login)
|
||||||
|
getCSRF()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save info about current [User] in memory
|
||||||
|
*/
|
||||||
|
private suspend fun getMyself(login: String) {
|
||||||
|
// WARNING: trailing / is important, 'cuz API devs are eating shit
|
||||||
|
val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
parameter("uns", login.drop(1))
|
||||||
|
}.bodyAsText())
|
||||||
|
|
||||||
|
val user = JSONObject(
|
||||||
|
client.get("$vuzapiURL/user") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
}.bodyAsText()
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("studentInfo", studentinfo.toString(2))
|
||||||
|
|
||||||
|
ME = User(
|
||||||
|
unnId = studentinfo.getString("id").toInt(),
|
||||||
|
bitrixId = user.getInt("bitrix_id"),
|
||||||
|
userId = user.getInt("id"),
|
||||||
|
type = when (studentinfo.getString("type")) {
|
||||||
|
"lecturer" -> Type.Lecturer // ig,,,
|
||||||
|
else -> Type.Student
|
||||||
|
},
|
||||||
|
email = user.getString("email"),
|
||||||
|
nameRu = user.getString("fullname"),
|
||||||
|
nameEn = user.getString("fullname_en"),
|
||||||
|
isMale = user.getString("sex") == "M",
|
||||||
|
birthday = Instant
|
||||||
|
.parse(user.getString("birthdate"))
|
||||||
|
.atZone(ZoneId.of("Europe/Moscow"))
|
||||||
|
.toLocalDate(),
|
||||||
|
avatar = user.getJSONObject("photo").let {
|
||||||
|
AvatarSet(
|
||||||
|
it.getString("orig"),
|
||||||
|
it.getString("thumbnail"),
|
||||||
|
it.getString("small"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getScheduleDay(
|
||||||
|
type: Type = ME.type,
|
||||||
|
id: Int = ME.unnId!!,
|
||||||
|
date: LocalDate
|
||||||
|
): ArrayList<ScheduleUnit> {
|
||||||
|
|
||||||
|
if ((type == ME.type) and (id == ME.unnId!!)) {
|
||||||
|
val schedule = withContext(Dispatchers.IO) { loadSchedule(date) }
|
||||||
|
Log.d("Schedule", schedule.joinToString())
|
||||||
|
if (schedule.isNotEmpty())
|
||||||
|
return schedule
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSchedule(type, id, date, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSchedule(
|
||||||
|
type: Type = ME.type,
|
||||||
|
id: Int = ME.unnId!!,
|
||||||
|
start: LocalDate,
|
||||||
|
finish: LocalDate
|
||||||
|
): ArrayList<ScheduleUnit> {
|
||||||
|
val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd")
|
||||||
|
|
||||||
|
val r = client.get("$ruzapiURL/schedule/${type.s}/$id") {
|
||||||
|
parameter("start", start.format(unnDatePattern))
|
||||||
|
parameter("finish", finish.format(unnDatePattern))
|
||||||
|
parameter("lng", "1")
|
||||||
|
}
|
||||||
|
val json = JSONArray(r.bodyAsText())
|
||||||
|
|
||||||
|
val out = arrayListOf<ScheduleUnit>()
|
||||||
|
for (i in 0 until json.length()) {
|
||||||
|
val unit = json.getJSONObject(i)
|
||||||
|
val lecturesJson = unit.getJSONArray("listOfLecturers")
|
||||||
|
val lecturers = arrayListOf<Lecturer>()
|
||||||
|
|
||||||
|
for (j in 0 until lecturesJson.length()) {
|
||||||
|
val lecturer = lecturesJson.getJSONObject(j)
|
||||||
|
lecturers.add(
|
||||||
|
Lecturer(
|
||||||
|
name = lecturer.getString("lecturer"),
|
||||||
|
email = lecturer.getString("lecturerEmail"),
|
||||||
|
unnId = lecturer.getInt("lecturerOid"),
|
||||||
|
uid = lecturer.getString("lecturerUID"),
|
||||||
|
rank = when (lecturer.getString("lecturer_rank")) {
|
||||||
|
"АССИСТ" -> LecturerRank.Assistant
|
||||||
|
"СТПРЕП" -> LecturerRank.SLecturer
|
||||||
|
"ДОЦЕНТ" -> LecturerRank.AProfessor
|
||||||
|
else -> LecturerRank.Lecturer
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.add(
|
||||||
|
ScheduleUnit(
|
||||||
|
oid = unit.getInt("lessonOid"),
|
||||||
|
auditorium = Auditorium(
|
||||||
|
name = unit.getString("auditorium"),
|
||||||
|
oid = unit.getInt("auditoriumOid"),
|
||||||
|
floor = unit.getInt("auditoriumfloor"),
|
||||||
|
building = Building(
|
||||||
|
name = unit.getString("building"),
|
||||||
|
gid = unit.getInt("buildingGid"),
|
||||||
|
oid = unit.getInt("buildingOid")
|
||||||
|
)
|
||||||
|
),
|
||||||
|
date = LocalDate.parse(unit.getString("date"), unnDatePattern),
|
||||||
|
discipline = Discipline(
|
||||||
|
name = unit.getString("discipline"),
|
||||||
|
oid = unit.getInt("disciplineOid"),
|
||||||
|
type = unit.getInt("disciplinetypeload")
|
||||||
|
),
|
||||||
|
kindOfWork = KindOfWork(
|
||||||
|
name = unit.getString("kindOfWork"),
|
||||||
|
complexity = unit.getInt("kindOfWorkComplexity"),
|
||||||
|
oid = unit.getInt("kindOfWorkOid"),
|
||||||
|
uid = unit.getString("kindOfWorkUid")
|
||||||
|
),
|
||||||
|
lecturers = lecturers,
|
||||||
|
stream = unit.getString("stream"),
|
||||||
|
begin = LocalTime.parse(
|
||||||
|
unit.getString("beginLesson"),
|
||||||
|
DateTimeFormatter.ofPattern("HH:mm")
|
||||||
|
),
|
||||||
|
end = LocalTime.parse(
|
||||||
|
unit.getString("endLesson"),
|
||||||
|
DateTimeFormatter.ofPattern("HH:mm")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((type == ME.type) and (id == ME.unnId!!)) {
|
||||||
|
cacheSchedule(out)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getCSRF() {
|
||||||
|
val r = client.get("$restURL/log.blogpost.get") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
parameter("sessid", "")
|
||||||
|
}
|
||||||
|
CSRF = JSONObject(r.bodyAsText()).getString("sessid")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getBlogposts(): ArrayList<Post> {
|
||||||
|
val r = client.get("$prtl2URL/news.php") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
header("x-bitrix-sessid-token", CSRF)
|
||||||
|
}
|
||||||
|
val result = JSONArray(r.bodyAsText())
|
||||||
|
|
||||||
|
val out = arrayListOf<Post>()
|
||||||
|
for (i in 0 until result.length()) {
|
||||||
|
val el = result.getJSONObject(i)
|
||||||
|
out.add(
|
||||||
|
Post(
|
||||||
|
id = el.getString("id").toInt(),
|
||||||
|
authorId = el.getJSONObject("author").getInt("id").toInt(),
|
||||||
|
enableComments = true, // FIXME: Delete the field or get correct value
|
||||||
|
numComments = el.getString("commentsnum").toInt(),
|
||||||
|
date = LocalDateTime.parse(
|
||||||
|
el.getString("time"),
|
||||||
|
DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
|
||||||
|
),
|
||||||
|
content = el.getString("fulltext")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUserByBitrixId(id: Int): User {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
loadUserByBitrixId(id)
|
||||||
|
}?.let { return it }
|
||||||
|
|
||||||
|
val userId = JSONObject(client.get("$vuzapiURL/user/bx/$id") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
}.bodyAsText()).getInt("id")
|
||||||
|
|
||||||
|
getUser(userId).let { user ->
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
cacheUser(user)
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getUser(id: Int): User {
|
||||||
|
val json = JSONObject(
|
||||||
|
client.get("$vuzapiURL/user/$id") {
|
||||||
|
header("Cookie", "PHPSESSID=$PHPSESSID")
|
||||||
|
}.bodyAsText()
|
||||||
|
)
|
||||||
|
|
||||||
|
Log.d("type", json.getJSONArray("profiles").getJSONObject(0).getString("type"))
|
||||||
|
|
||||||
|
return User(
|
||||||
|
unnId = null,
|
||||||
|
bitrixId = json.getInt("bitrix_id"),
|
||||||
|
userId = json.getInt("id"),
|
||||||
|
type = if (json.getJSONArray("profiles").getJSONObject(0)
|
||||||
|
.getString("type") == "employee"
|
||||||
|
) Type.Employee else Type.Student,
|
||||||
|
email = json.getString("email"),
|
||||||
|
nameRu = json.getString("fullname"),
|
||||||
|
nameEn = json.getString("fullname_en"),
|
||||||
|
isMale = json.getString("sex") == "M",
|
||||||
|
birthday = Instant
|
||||||
|
.parse(json.getString("birthdate"))
|
||||||
|
.atZone(ZoneId.of("Europe/Moscow"))
|
||||||
|
.toLocalDate(),
|
||||||
|
avatar = json.getJSONObject("photo").let {
|
||||||
|
AvatarSet(
|
||||||
|
it.getString("orig"),
|
||||||
|
it.getString("thumbnail"),
|
||||||
|
it.getString("small"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package ru.sweetbread.unn
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import org.acra.config.httpSender
|
||||||
|
import org.acra.data.StringFormat
|
||||||
|
import org.acra.ktx.initAcra
|
||||||
|
import org.acra.sender.HttpSender
|
||||||
|
|
||||||
|
class UNNApp : Application() {
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
super.attachBaseContext(base)
|
||||||
|
|
||||||
|
// initAcra {
|
||||||
|
// buildConfigClass = BuildConfig::class.java
|
||||||
|
// reportFormat = StringFormat.JSON
|
||||||
|
// httpSender {
|
||||||
|
// uri = BuildConfig.ACRA_URL
|
||||||
|
// basicAuthLogin = BuildConfig.ACRA_LOGIN
|
||||||
|
// basicAuthPassword = BuildConfig.ACRA_PASS
|
||||||
|
// httpMethod = HttpSender.Method.POST
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package ru.sweetbread.unn.db
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
|
||||||
|
@Database(entities = [
|
||||||
|
UserDB::class,
|
||||||
|
BuildingDB::class,
|
||||||
|
AuditoriumDB::class,
|
||||||
|
DisciplineDB::class,
|
||||||
|
KindOfWorkDB::class,
|
||||||
|
LecturerDB::class,
|
||||||
|
ScheduleUnitDB::class], version = 1)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun userDao(): UserDao
|
||||||
|
abstract fun scheduleDao(): ScheduleItemDao
|
||||||
|
abstract fun auditoriumDao(): AuditoriumDao
|
||||||
|
abstract fun buildingDao(): BuildingDao
|
||||||
|
abstract fun disciplineDao(): DisciplineDao
|
||||||
|
abstract fun kindOfWorkDao(): KindOfWorkDao
|
||||||
|
abstract fun lecturerDao(): LecturerDao
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
package ru.sweetbread.unn.db
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.Query
|
||||||
|
import ru.sweetbread.unn.Auditorium
|
||||||
|
import ru.sweetbread.unn.Building
|
||||||
|
import ru.sweetbread.unn.Discipline
|
||||||
|
import ru.sweetbread.unn.KindOfWork
|
||||||
|
import ru.sweetbread.unn.Lecturer
|
||||||
|
import ru.sweetbread.unn.LecturerRank
|
||||||
|
import ru.sweetbread.unn.ScheduleUnit
|
||||||
|
import ru.sweetbread.unn.ui.layout.db
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.LocalTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class BuildingDB(
|
||||||
|
@PrimaryKey val oid: Int,
|
||||||
|
@ColumnInfo val name: String,
|
||||||
|
@ColumnInfo val gid: Int,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface BuildingDao {
|
||||||
|
@Query("SELECT * FROM buildingdb WHERE oid = :oid LIMIT 1")
|
||||||
|
fun get(oid: Int): BuildingDB?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(building: BuildingDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(building: BuildingDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheBuilding(building: Building) {
|
||||||
|
db.buildingDao().insert(
|
||||||
|
BuildingDB(
|
||||||
|
building.oid,
|
||||||
|
building.name,
|
||||||
|
building.gid,
|
||||||
|
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadBuilding(oid: Int): Building? {
|
||||||
|
return db.buildingDao().get(oid)?.let {
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) > LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.buildingDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
Building(
|
||||||
|
it.name,
|
||||||
|
it.gid,
|
||||||
|
it.oid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class AuditoriumDB(
|
||||||
|
@PrimaryKey val oid: Int,
|
||||||
|
@ColumnInfo val name: String,
|
||||||
|
@ColumnInfo val floor: Int?,
|
||||||
|
@ColumnInfo val buildingOid: Int,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface AuditoriumDao {
|
||||||
|
@Query("SELECT * FROM auditoriumdb WHERE oid = :oid LIMIT 1")
|
||||||
|
fun get(oid: Int): AuditoriumDB?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(auditorium: AuditoriumDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(auditorium: AuditoriumDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheAuditorium(auditorium: Auditorium) {
|
||||||
|
cacheBuilding(auditorium.building)
|
||||||
|
db.auditoriumDao().insert(
|
||||||
|
AuditoriumDB(
|
||||||
|
auditorium.oid,
|
||||||
|
auditorium.name,
|
||||||
|
auditorium.floor,
|
||||||
|
auditorium.building.oid,
|
||||||
|
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadAuditorium(oid: Int): Auditorium? {
|
||||||
|
return db.auditoriumDao().get(oid)?.let {
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) > LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.auditoriumDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val building = loadBuilding(it.buildingOid) ?: return null
|
||||||
|
return Auditorium(
|
||||||
|
it.name,
|
||||||
|
it.oid,
|
||||||
|
it.floor ?: 0,
|
||||||
|
building
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class DisciplineDB(
|
||||||
|
@PrimaryKey val oid: Int,
|
||||||
|
@ColumnInfo val name: String,
|
||||||
|
@ColumnInfo val type: Int,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface DisciplineDao {
|
||||||
|
@Query("SELECT * FROM disciplinedb WHERE oid = :oid LIMIT 1")
|
||||||
|
fun get(oid: Int): DisciplineDB?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(discipline: DisciplineDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(discipline: DisciplineDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheDiscipline(discipline: Discipline) {
|
||||||
|
db.disciplineDao().insert(
|
||||||
|
DisciplineDB(
|
||||||
|
discipline.oid,
|
||||||
|
discipline.name,
|
||||||
|
discipline.type,
|
||||||
|
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadDiscipline(oid: Int): Discipline? {
|
||||||
|
return db.disciplineDao().get(oid)?.let {
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) > LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.disciplineDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Discipline(
|
||||||
|
it.name,
|
||||||
|
it.oid,
|
||||||
|
it.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class KindOfWorkDB(
|
||||||
|
@PrimaryKey val oid: Int,
|
||||||
|
@ColumnInfo val name: String,
|
||||||
|
@ColumnInfo val uid: String,
|
||||||
|
@ColumnInfo val complexity: Int,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface KindOfWorkDao {
|
||||||
|
@Query("SELECT * FROM kindofworkdb WHERE oid = :oid LIMIT 1")
|
||||||
|
fun get(oid: Int): KindOfWorkDB?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(kindOfWork: KindOfWorkDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(kindOfWork: KindOfWorkDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheKindOfWork(kindOfWork: KindOfWork) {
|
||||||
|
db.kindOfWorkDao().insert(
|
||||||
|
KindOfWorkDB(
|
||||||
|
kindOfWork.oid,
|
||||||
|
kindOfWork.name,
|
||||||
|
kindOfWork.uid,
|
||||||
|
kindOfWork.complexity,
|
||||||
|
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadKindOfWork(oid: Int): KindOfWork? {
|
||||||
|
return db.kindOfWorkDao().get(oid)?.let {
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) > LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.kindOfWorkDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return KindOfWork(
|
||||||
|
it.name,
|
||||||
|
it.oid,
|
||||||
|
it.uid,
|
||||||
|
it.complexity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class LecturerDB(
|
||||||
|
@PrimaryKey val unnId: Int,
|
||||||
|
@ColumnInfo val name: String,
|
||||||
|
@ColumnInfo val rank: LecturerRank,
|
||||||
|
@ColumnInfo val email: String,
|
||||||
|
@ColumnInfo val uid: String,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface LecturerDao {
|
||||||
|
@Query("SELECT * FROM lecturerdb WHERE unnId = :unnId LIMIT 1")
|
||||||
|
fun get(unnId: Int): LecturerDB?
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(lecturer: LecturerDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(lecturer: LecturerDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheLecturer(lecturer: Lecturer) {
|
||||||
|
db.lecturerDao().insert(
|
||||||
|
LecturerDB(
|
||||||
|
lecturer.unnId,
|
||||||
|
lecturer.name,
|
||||||
|
lecturer.rank,
|
||||||
|
lecturer.email,
|
||||||
|
lecturer.uid,
|
||||||
|
LocalDateTime.now().plusMonths(1).format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadLecturer(unnId: Int): Lecturer? {
|
||||||
|
return db.lecturerDao().get(unnId)?.let {
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) > LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.lecturerDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Lecturer(
|
||||||
|
it.name,
|
||||||
|
it.rank,
|
||||||
|
it.email,
|
||||||
|
it.unnId,
|
||||||
|
it.uid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class ScheduleUnitDB(
|
||||||
|
@PrimaryKey val oid: Int,
|
||||||
|
@ColumnInfo val date: String,
|
||||||
|
@ColumnInfo val stream: String,
|
||||||
|
@ColumnInfo val begin: String,
|
||||||
|
@ColumnInfo val end: String,
|
||||||
|
|
||||||
|
@ColumnInfo val auditoriumOid: Int,
|
||||||
|
@ColumnInfo val disciplineOid: Int,
|
||||||
|
@ColumnInfo val kindOfWorkOid: Int,
|
||||||
|
@ColumnInfo val lecturerId: Int, // TODO: many-to-many
|
||||||
|
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
@Dao
|
||||||
|
interface ScheduleItemDao {
|
||||||
|
@Query("SELECT * FROM scheduleUnitDB WHERE oid = :oid LIMIT 1")
|
||||||
|
fun getSchedule(oid: Int): ScheduleUnitDB?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM scheduleUnitDB WHERE date = :date ORDER BY `begin`")
|
||||||
|
fun getSchedule(date: String): List<ScheduleUnitDB>
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun insert(user: ScheduleUnitDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(user: ScheduleUnitDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheSchedule(item: ScheduleUnit) {
|
||||||
|
cacheAuditorium(item.auditorium)
|
||||||
|
cacheDiscipline(item.discipline)
|
||||||
|
cacheKindOfWork(item.kindOfWork)
|
||||||
|
cacheLecturer(item.lecturers[0])
|
||||||
|
|
||||||
|
db.scheduleDao().insert(
|
||||||
|
ScheduleUnitDB(
|
||||||
|
item.oid,
|
||||||
|
item.date.format(DateTimeFormatter.ISO_DATE),
|
||||||
|
item.stream,
|
||||||
|
item.begin.format(DateTimeFormatter.ISO_TIME),
|
||||||
|
item.end.format(DateTimeFormatter.ISO_TIME),
|
||||||
|
item.auditorium.oid,
|
||||||
|
item.discipline.oid,
|
||||||
|
item.kindOfWork.oid,
|
||||||
|
item.lecturers[0].unnId,
|
||||||
|
when {
|
||||||
|
(LocalDate.now() > item.date) -> LocalDateTime.now().plusWeeks(2)
|
||||||
|
(LocalDate.now().plusWeeks(1) > item.date) -> LocalDateTime.now().plusDays(1)
|
||||||
|
(LocalDate.now().plusWeeks(2) > item.date) -> LocalDateTime.now().plusWeeks(1)
|
||||||
|
else -> LocalDateTime.now().plusWeeks(2)
|
||||||
|
}.format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cacheSchedule(items: ArrayList<ScheduleUnit>) {
|
||||||
|
for (item in items)
|
||||||
|
cacheSchedule(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSchedule(oid: Int): ScheduleUnit? {
|
||||||
|
db.scheduleDao().getSchedule(oid)?.let {
|
||||||
|
Log.d("load", it.oid.toString())
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
it.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_DATE_TIME
|
||||||
|
) < LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
Log.d("delete", it.oid.toString())
|
||||||
|
db.scheduleDao().delete(it)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScheduleUnit(
|
||||||
|
it.oid,
|
||||||
|
loadAuditorium(it.auditoriumOid) ?: return null,
|
||||||
|
LocalDate.parse(it.date, DateTimeFormatter.ISO_DATE),
|
||||||
|
loadDiscipline(it.disciplineOid) ?: return null,
|
||||||
|
loadKindOfWork(it.kindOfWorkOid) ?: return null,
|
||||||
|
arrayListOf(loadLecturer(it.lecturerId) ?: return null),
|
||||||
|
it.stream,
|
||||||
|
LocalTime.parse(it.begin, DateTimeFormatter.ISO_TIME),
|
||||||
|
LocalTime.parse(it.end, DateTimeFormatter.ISO_TIME)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSchedule(date: LocalDate): ArrayList<ScheduleUnit> {
|
||||||
|
db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
||||||
|
.map { Log.d("meow", "${it.oid}: ${loadSchedule(it.oid)}") }
|
||||||
|
return ArrayList(
|
||||||
|
db.scheduleDao().getSchedule(date.format(DateTimeFormatter.ISO_DATE))
|
||||||
|
.mapNotNull { loadSchedule(it.oid) }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package ru.sweetbread.unn.db
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import androidx.room.Query
|
||||||
|
import ru.sweetbread.unn.AvatarSet
|
||||||
|
import ru.sweetbread.unn.Type
|
||||||
|
import ru.sweetbread.unn.User
|
||||||
|
import ru.sweetbread.unn.ui.layout.db
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class UserDB(
|
||||||
|
@PrimaryKey val userId: Int,
|
||||||
|
@ColumnInfo val unnId: Int?,
|
||||||
|
@ColumnInfo val bitrixId: Int,
|
||||||
|
@ColumnInfo val type: Type,
|
||||||
|
@ColumnInfo val email: String,
|
||||||
|
@ColumnInfo val nameRu: String,
|
||||||
|
@ColumnInfo val nameEn: String,
|
||||||
|
@ColumnInfo val isMale: Boolean,
|
||||||
|
@ColumnInfo val birthday: String,
|
||||||
|
@ColumnInfo val origAvatar: String,
|
||||||
|
@ColumnInfo val thumbAvatar: String,
|
||||||
|
@ColumnInfo val smallAvatar: String,
|
||||||
|
@ColumnInfo val expiredAt: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface UserDao {
|
||||||
|
@Query("SELECT * FROM userDB WHERE bitrixId = :bitrixId LIMIT 1")
|
||||||
|
fun getUserByBitrix(bitrixId: Int): UserDB?
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
fun insert(user: UserDB)
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
fun delete(user: UserDB)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun cacheUser(user: User) {
|
||||||
|
try {
|
||||||
|
db.userDao().insert(
|
||||||
|
UserDB(
|
||||||
|
user.userId,
|
||||||
|
user.unnId,
|
||||||
|
user.bitrixId,
|
||||||
|
user.type,
|
||||||
|
user.email,
|
||||||
|
user.nameRu,
|
||||||
|
user.nameEn,
|
||||||
|
user.isMale,
|
||||||
|
user.birthday.format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
|
user.avatar.original,
|
||||||
|
user.avatar.thumbnail,
|
||||||
|
user.avatar.small,
|
||||||
|
LocalDateTime.now().plusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (_: android.database.sqlite.SQLiteConstraintException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadUserByBitrixId(bitrixId: Int): User? {
|
||||||
|
val user = db.userDao().getUserByBitrix(bitrixId)
|
||||||
|
Log.d("UserDB", user?.nameEn ?: "None")
|
||||||
|
if (user == null) return null
|
||||||
|
if (LocalDateTime.parse(
|
||||||
|
user.expiredAt,
|
||||||
|
DateTimeFormatter.ISO_LOCAL_DATE_TIME
|
||||||
|
) < LocalDateTime.now()
|
||||||
|
) {
|
||||||
|
db.userDao().delete(user)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return User(
|
||||||
|
user.unnId,
|
||||||
|
user.bitrixId,
|
||||||
|
user.userId,
|
||||||
|
user.type,
|
||||||
|
user.email,
|
||||||
|
user.nameRu,
|
||||||
|
user.nameEn,
|
||||||
|
user.isMale,
|
||||||
|
LocalDate.parse(user.birthday, DateTimeFormatter.ISO_LOCAL_DATE),
|
||||||
|
AvatarSet(
|
||||||
|
user.origAvatar,
|
||||||
|
user.thumbAvatar,
|
||||||
|
user.smallAvatar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package ru.sweetbread.unn.ui
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import io.ktor.client.request.forms.submitForm
|
|
||||||
import io.ktor.client.request.get
|
|
||||||
import io.ktor.client.request.parameter
|
|
||||||
import io.ktor.client.statement.bodyAsText
|
|
||||||
import io.ktor.http.parameters
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
import ru.sweetbread.unn.ui.layout.LoginData
|
|
||||||
import ru.sweetbread.unn.ui.layout.client
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
private lateinit var PHPSESSID: String
|
|
||||||
lateinit var ME: User
|
|
||||||
|
|
||||||
const val portalURL = "https://portal.unn.ru"
|
|
||||||
const val ruzapiURL = "$portalURL/ruzapi"
|
|
||||||
|
|
||||||
enum class Type(val s: String) {
|
|
||||||
Student("student"),
|
|
||||||
Group("group"),
|
|
||||||
Lecturer("lecturer"),
|
|
||||||
Auditorium("auditorium")
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class LecturerRank(val s: String) {
|
|
||||||
Lecturer("Lecturer"),
|
|
||||||
SLecturer("Senior Lecturer")
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScheduleUnit(
|
|
||||||
val auditorium: Auditorium,
|
|
||||||
val date: LocalDate,
|
|
||||||
val discipline: Discipline,
|
|
||||||
val kindOfWork: KindOfWork,
|
|
||||||
val lecturers: ArrayList<Lecturer>,
|
|
||||||
val stream: String,
|
|
||||||
val begin: LocalTime,
|
|
||||||
val end: LocalTime)
|
|
||||||
|
|
||||||
class Auditorium (val name: String,
|
|
||||||
val oid: Int,
|
|
||||||
val floor: Int,
|
|
||||||
val building: Building)
|
|
||||||
class Building(val name: String,
|
|
||||||
val gid: Int,
|
|
||||||
val oid: Int)
|
|
||||||
|
|
||||||
class Discipline (val name: String,
|
|
||||||
val oid: Int,
|
|
||||||
val type: Int)
|
|
||||||
|
|
||||||
class KindOfWork (val name: String,
|
|
||||||
val oid: Int,
|
|
||||||
val uid: String,
|
|
||||||
val complexity: Int)
|
|
||||||
|
|
||||||
class Lecturer (val name: String,
|
|
||||||
val rank: LecturerRank,
|
|
||||||
val email: String,
|
|
||||||
val oid: Int,
|
|
||||||
val uid: String)
|
|
||||||
|
|
||||||
class User (val id: String,
|
|
||||||
val uns: String,
|
|
||||||
val type: Type,
|
|
||||||
val email: String,
|
|
||||||
val name: String,
|
|
||||||
val info: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorize user by [login] and [password]
|
|
||||||
*
|
|
||||||
* Also defines local vars [PHPSESSID] and [ME.id]
|
|
||||||
*/
|
|
||||||
suspend fun auth(login: String = LoginData.login, password: String = LoginData.password, forced: Boolean = false): Boolean {
|
|
||||||
if (!forced) {
|
|
||||||
if (::PHPSESSID.isInitialized and ::ME.isInitialized)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
val r = client.submitForm("$portalURL/auth/?login=yes",
|
|
||||||
formParameters = parameters {
|
|
||||||
append("AUTH_FORM", "Y")
|
|
||||||
append("TYPE", "AUTH")
|
|
||||||
append("backurl", "/")
|
|
||||||
append("USER_LOGIN", login)
|
|
||||||
append("USER_PASSWORD", password)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (r.status.value == 302) {
|
|
||||||
PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1]
|
|
||||||
getMyself(login)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getMyself(login: String) {
|
|
||||||
val r = client.get("$ruzapiURL/studentinfo") {
|
|
||||||
parameter("uns", login.substring(1))
|
|
||||||
}
|
|
||||||
val json = JSONObject(r.bodyAsText())
|
|
||||||
ME = User(
|
|
||||||
id = json.getString("id"),
|
|
||||||
uns = json.getString("uns"),
|
|
||||||
type = when(json.getString("type")) {
|
|
||||||
"lecturer" -> Type.Lecturer // ig,,,
|
|
||||||
else -> Type.Student
|
|
||||||
},
|
|
||||||
email = json.getString("email"),
|
|
||||||
name = json.getString("fio"),
|
|
||||||
info = json.getString("info")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getSchedule(type: Type = Type.Student, id: String = ME.id, start: LocalDate, finish: LocalDate): ArrayList<ScheduleUnit> {
|
|
||||||
val r = client.get("$ruzapiURL/schedule/${type.s}/$id") {
|
|
||||||
parameter("start", start.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
|
|
||||||
parameter("finish", finish.format(DateTimeFormatter.ofPattern("yyyy.MM.dd")))
|
|
||||||
parameter("lng", "1")
|
|
||||||
}
|
|
||||||
val json = JSONArray(r.bodyAsText())
|
|
||||||
val out = arrayListOf<ScheduleUnit>()
|
|
||||||
for (i in 0 until json.length()) {
|
|
||||||
val unit = json.getJSONObject(i)
|
|
||||||
val lecturesJson = unit.getJSONArray("listOfLecturers")
|
|
||||||
val lecturers = arrayListOf<Lecturer>()
|
|
||||||
|
|
||||||
for (j in 0 until lecturesJson.length()) {
|
|
||||||
val lecturer = lecturesJson.getJSONObject(j)
|
|
||||||
lecturers.add(
|
|
||||||
Lecturer(
|
|
||||||
name = lecturer.getString("lecturer"),
|
|
||||||
email = lecturer.getString("lecturerEmail"),
|
|
||||||
oid = lecturer.getInt("lecturerOid"),
|
|
||||||
uid = lecturer.getString("lecturerUID"),
|
|
||||||
rank = when (lecturer.getString("lecturer_rank")) {
|
|
||||||
"СТПРЕП" -> LecturerRank.SLecturer
|
|
||||||
else -> LecturerRank.Lecturer
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.add(
|
|
||||||
ScheduleUnit(
|
|
||||||
auditorium = Auditorium(
|
|
||||||
name = unit.getString("auditorium"),
|
|
||||||
oid = unit.getInt("auditoriumOid"),
|
|
||||||
floor = unit.getInt("auditoriumfloor"),
|
|
||||||
building = Building(
|
|
||||||
name = unit.getString("building"),
|
|
||||||
gid = unit.getInt("buildingGid"),
|
|
||||||
oid = unit.getInt("buildingOid")
|
|
||||||
)
|
|
||||||
),
|
|
||||||
date = LocalDate.parse(unit.getString("date"), DateTimeFormatter.ofPattern("yyyy.MM.dd")),
|
|
||||||
discipline = Discipline(
|
|
||||||
name = unit.getString("discipline"),
|
|
||||||
oid = unit.getInt("disciplineOid"),
|
|
||||||
type = unit.getInt("disciplinetypeload")
|
|
||||||
),
|
|
||||||
kindOfWork = KindOfWork(
|
|
||||||
name = unit.getString("kindOfWork"),
|
|
||||||
complexity = unit.getInt("kindOfWorkComplexity"),
|
|
||||||
oid = unit.getInt("kindOfWorkOid"),
|
|
||||||
uid = unit.getString("kindOfWorkUid")
|
|
||||||
),
|
|
||||||
lecturers = lecturers,
|
|
||||||
stream = unit.getString("stream"),
|
|
||||||
begin = LocalTime.parse(unit.getString("beginLesson"), DateTimeFormatter.ofPattern("HH:mm")),
|
|
||||||
end = LocalTime.parse(unit.getString("endLesson"), DateTimeFormatter.ofPattern("HH:mm"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2025. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ru.sweetbread.unn.ui.composes
|
||||||
|
|
||||||
|
import android.text.util.Linkify
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.NonRestartableComposable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.google.android.material.textview.MaterialTextView
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import org.kefirsf.bb.BBProcessorFactory
|
||||||
|
import ru.sweetbread.unn.AvatarSet
|
||||||
|
import ru.sweetbread.unn.Post
|
||||||
|
import ru.sweetbread.unn.R
|
||||||
|
import ru.sweetbread.unn.Type
|
||||||
|
import ru.sweetbread.unn.User
|
||||||
|
import ru.sweetbread.unn.getBlogposts
|
||||||
|
import ru.sweetbread.unn.getUserByBitrixId
|
||||||
|
import ru.sweetbread.unn.portalURL
|
||||||
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.format.FormatStyle
|
||||||
|
|
||||||
|
|
||||||
|
val defUser = User(
|
||||||
|
null,
|
||||||
|
123,
|
||||||
|
123,
|
||||||
|
Type.Student,
|
||||||
|
"cool.email@domain.com",
|
||||||
|
"Джон Сигма Омегович",
|
||||||
|
"Jon Sigma Omega",
|
||||||
|
true,
|
||||||
|
LocalDate.now(),
|
||||||
|
AvatarSet(
|
||||||
|
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg",
|
||||||
|
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg",
|
||||||
|
"https://upload.wikimedia.org/wikipedia/ru/thumb/9/94/%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg/500px-%D0%93%D0%B8%D0%B3%D0%B0%D1%87%D0%B0%D0%B4.jpg"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Blogposts(viewModel: PostViewModel = viewModel()) {
|
||||||
|
val posts by viewModel.posts.collectAsState()
|
||||||
|
var extended by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.loadPosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posts.isNotEmpty()) {
|
||||||
|
Log.d("Another fuck", posts.size.toString())
|
||||||
|
LazyColumn {
|
||||||
|
items(posts) {
|
||||||
|
PostItem(
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||||
|
.clickable {
|
||||||
|
extended = if (extended == it.id) 0 else it.id
|
||||||
|
},
|
||||||
|
post = it,
|
||||||
|
extended == it.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
trackColor = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PostRepository {
|
||||||
|
suspend fun loadPosts(): List<Post> {
|
||||||
|
return getBlogposts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostViewModel : ViewModel() {
|
||||||
|
private val repository = PostRepository()
|
||||||
|
private val _posts = MutableStateFlow<List<Post>>(emptyList())
|
||||||
|
val posts: StateFlow<List<Post>> = _posts.asStateFlow()
|
||||||
|
|
||||||
|
suspend fun loadPosts() {
|
||||||
|
_posts.value = repository.loadPosts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun UserItem(modifier: Modifier = Modifier, user: User, info: String? = null) {
|
||||||
|
Row(
|
||||||
|
modifier.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(RoundedCornerShape(50)),
|
||||||
|
model = portalURL + user.avatar.thumbnail,
|
||||||
|
contentDescription = user.nameEn
|
||||||
|
)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(user.nameRu, fontWeight = FontWeight.Bold)
|
||||||
|
if (!info.isNullOrBlank())
|
||||||
|
Text(
|
||||||
|
text = info,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontSize = MaterialTheme.typography.labelLarge.fontSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@NonRestartableComposable
|
||||||
|
fun PostItem(modifier: Modifier = Modifier, post: Post, extended: Boolean = false) {
|
||||||
|
var user: User? by remember { mutableStateOf(null) }
|
||||||
|
val processor = remember { BBProcessorFactory.getInstance().create() }
|
||||||
|
var html: String by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
|
||||||
|
LaunchedEffect(post) {
|
||||||
|
html = post.content
|
||||||
|
user = getUserByBitrixId(post.authorId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier.padding(16.dp)) {
|
||||||
|
if (user != null)
|
||||||
|
UserItem(user = user!!)
|
||||||
|
else
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
trackColor = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
|
|
||||||
|
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||||
|
val linkColor = MaterialTheme.colorScheme.primary.toArgb()
|
||||||
|
|
||||||
|
AndroidView(
|
||||||
|
factory = {
|
||||||
|
MaterialTextView(it).apply {
|
||||||
|
autoLinkMask = Linkify.WEB_URLS
|
||||||
|
linksClickable = true
|
||||||
|
setTextColor(textColor)
|
||||||
|
setLinkTextColor(linkColor)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update = {
|
||||||
|
it.maxLines = if (extended) Int.MAX_VALUE else 5
|
||||||
|
it.text = HtmlCompat.fromHtml(html, 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = post.date.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun UserItemPreview() {
|
||||||
|
UNNTheme {
|
||||||
|
Surface {
|
||||||
|
UserItem(
|
||||||
|
Modifier
|
||||||
|
.width(300.dp)
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer), defUser, Type.Student.s
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PostItemPreview() {
|
||||||
|
val post = Post(
|
||||||
|
id = 154923,
|
||||||
|
authorId = 165945,
|
||||||
|
enableComments = true,
|
||||||
|
numComments = 0,
|
||||||
|
date = LocalDateTime.of(2024, 3, 20, 18, 55, 20),
|
||||||
|
content = stringResource(id = R.string.lorem)
|
||||||
|
)
|
||||||
|
|
||||||
|
UNNTheme {
|
||||||
|
Surface {
|
||||||
|
PostItem(
|
||||||
|
Modifier
|
||||||
|
.clip(RoundedCornerShape(8.dp))
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer), post
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
package ru.sweetbread.unn.ui.composes
|
package ru.sweetbread.unn.ui.composes
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
@@ -20,6 +25,8 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -27,33 +34,43 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import com.kizitonwose.calendar.compose.WeekCalendar
|
import com.kizitonwose.calendar.compose.WeekCalendar
|
||||||
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
|
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.sweetbread.unn.ui.Auditorium
|
import ru.sweetbread.unn.Auditorium
|
||||||
import ru.sweetbread.unn.ui.Building
|
import ru.sweetbread.unn.Building
|
||||||
import ru.sweetbread.unn.ui.Discipline
|
import ru.sweetbread.unn.Discipline
|
||||||
import ru.sweetbread.unn.ui.KindOfWork
|
import ru.sweetbread.unn.KindOfWork
|
||||||
import ru.sweetbread.unn.ui.Lecturer
|
import ru.sweetbread.unn.Lecturer
|
||||||
import ru.sweetbread.unn.ui.LecturerRank
|
import ru.sweetbread.unn.LecturerRank
|
||||||
import ru.sweetbread.unn.ui.ScheduleUnit
|
import ru.sweetbread.unn.R
|
||||||
import ru.sweetbread.unn.ui.getSchedule
|
import ru.sweetbread.unn.ScheduleUnit
|
||||||
|
import ru.sweetbread.unn.getScheduleDay
|
||||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
|
import splitties.resources.appStr
|
||||||
import java.time.DayOfWeek
|
import java.time.DayOfWeek
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Schedule() {
|
fun Schedule() {
|
||||||
val state = rememberWeekCalendarState(
|
val state = rememberWeekCalendarState(
|
||||||
firstDayOfWeek = DayOfWeek.MONDAY
|
firstDayOfWeek = DayOfWeek.MONDAY // TODO: set start and end weeks to September and July of current year
|
||||||
)
|
)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -66,14 +83,10 @@ fun Schedule() {
|
|||||||
.padding(vertical = 16.dp)
|
.padding(vertical = 16.dp)
|
||||||
.aspectRatio(1f) // This is important for square sizing!
|
.aspectRatio(1f) // This is important for square sizing!
|
||||||
.offset(2.dp)
|
.offset(2.dp)
|
||||||
.background(if (it.date == curDate) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.secondaryContainer)
|
.background(if (it.date == curDate) MaterialTheme.colorScheme.inversePrimary else MaterialTheme.colorScheme.surfaceContainer)
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = {
|
onClick = { curDate = it.date },
|
||||||
curDate = it.date
|
enabled = curDate != it.date
|
||||||
Log.d("Here bug (olClick)",
|
|
||||||
curDate.format(DateTimeFormatter.ISO_DATE)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
@@ -93,13 +106,20 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
|
var loadedDate by remember { mutableStateOf(LocalDate.MIN) }
|
||||||
val lessons = remember { mutableListOf<ScheduleUnit>() }
|
val lessons = remember { mutableListOf<ScheduleUnit>() }
|
||||||
|
var expanded by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
if (loadedDate == date) {
|
if (loadedDate == date) {
|
||||||
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
|
Log.d("Loaded", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
|
||||||
LazyColumn (modifier) {
|
LazyColumn (modifier) {
|
||||||
items(lessons) {
|
items(lessons) {
|
||||||
ScheduleItem(unit = it)
|
ScheduleItem(unit = it, modifier = Modifier.clickable {
|
||||||
|
expanded = if (it.oid == expanded) 0
|
||||||
|
else it.oid
|
||||||
|
}, expanded = expanded == it.oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lessons.isEmpty())
|
||||||
|
item { Text(appStr(R.string.noData)) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
@@ -112,53 +132,214 @@ fun ScheduleDay(modifier: Modifier = Modifier, date: LocalDate) {
|
|||||||
LaunchedEffect(date != loadedDate) {
|
LaunchedEffect(date != loadedDate) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
lessons.clear()
|
lessons.clear()
|
||||||
lessons.addAll(getSchedule(start = date, finish = date))
|
lessons.addAll(getScheduleDay(date = date))
|
||||||
loadedDate = date
|
loadedDate = date
|
||||||
Log.d("Loading", "${date.format(DateTimeFormatter.ISO_DATE)} ${lessons.size}")
|
|
||||||
Log.d("Here bug", "${loadedDate.format(DateTimeFormatter.ISO_DATE)} ${date.format(DateTimeFormatter.ISO_DATE)}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit) {
|
fun ScheduleItem(modifier: Modifier = Modifier, unit: ScheduleUnit, expanded: Boolean = false) {
|
||||||
|
fun getRel(begin: Long, end: Long, now: Long): Float {
|
||||||
|
if ((begin > now) or (now > end))
|
||||||
|
return -1f
|
||||||
|
return (now - begin) / (end - begin).toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
|
val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
|
var rel by remember {
|
||||||
|
mutableFloatStateOf(getRel(
|
||||||
|
LocalDateTime.of(unit.date, unit.begin).atZone(ZoneId.of("Europe/Moscow")).toEpochSecond(),
|
||||||
|
LocalDateTime.of(unit.date, unit.end).atZone(ZoneId.of("Europe/Moscow")).toEpochSecond(),
|
||||||
|
LocalDateTime.now().atZone(ZoneId.of("Europe/Moscow")).toEpochSecond()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val calendar = Calendar.getInstance().apply { timeInMillis = now }
|
||||||
|
val seconds = calendar.get(Calendar.SECOND)
|
||||||
|
val millisUntilNextMinute = (60 - seconds) * 1000L - calendar.get(Calendar.MILLISECOND)
|
||||||
|
|
||||||
|
delay(millisUntilNextMinute)
|
||||||
|
rel = getRel(
|
||||||
|
LocalDateTime.of(unit.date, unit.begin).atZone(ZoneId.of("Europe/Moscow")).toEpochSecond(),
|
||||||
|
LocalDateTime.of(unit.date, unit.end).atZone(ZoneId.of("Europe/Moscow")).toEpochSecond(),
|
||||||
|
LocalDateTime.now().atZone(ZoneId.of("Europe/Moscow")).toEpochSecond()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row (
|
Row (
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(4.dp)
|
.padding(4.dp)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
.background(
|
||||||
|
if (rel != -1f)
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
)
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
){
|
){
|
||||||
Column (Modifier.weight(1f)) {
|
Column (Modifier.weight(1f)) {
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = unit.discipline.name,
|
text = unit.discipline.name,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.zIndex(1f),
|
modifier = Modifier.zIndex(1f),
|
||||||
maxLines = 1,
|
maxLines = if (expanded) Int.MAX_VALUE else 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(text = unit.kindOfWork.name, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
|
||||||
Row (Modifier) {
|
|
||||||
Text(text = unit.auditorium.name, fontWeight = FontWeight.Bold, modifier = Modifier.padding(end = 4.dp))
|
|
||||||
Text(text = unit.auditorium.building.name, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility (expanded) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
val begin = unit.begin.format(DateTimeFormatter.ofPattern("HH:mm"))
|
Text(
|
||||||
val end = unit.end.format(DateTimeFormatter.ofPattern("HH:mm"))
|
text = unit.kindOfWork.name,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
AnimatedVisibility (expanded) {
|
||||||
|
Text(text = unit.stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility (expanded) {
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility (!expanded) {
|
||||||
|
Row(Modifier) {
|
||||||
|
Text(
|
||||||
|
text = unit.auditorium.name,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = unit.auditorium.building.name,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility (expanded) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = "${stringResource(R.string.auditorium)}: ${unit.auditorium.name}",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
modifier = Modifier.padding(end = 4.dp)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${stringResource(R.string.building)}: ${unit.auditorium.building.name}",
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
if (unit.auditorium.floor != 0) {
|
||||||
|
Text(
|
||||||
|
text = "${stringResource(R.string.floor)}: ${unit.auditorium.floor}",
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(text = unit.lecturers[0].name, fontWeight = FontWeight.Bold)
|
||||||
|
Text(text = stringResource(unit.lecturers[0].rank.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 16.dp),
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
|
||||||
|
Row (Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(begin.toString(), fontWeight = FontWeight.Bold)
|
||||||
|
if (rel != -1f)
|
||||||
|
DividerWithMarker(
|
||||||
|
Modifier.weight(1f).padding(horizontal = 2.dp),
|
||||||
|
positionPercentage = rel,
|
||||||
|
color = MaterialTheme.colorScheme.outline,
|
||||||
|
thickness = 3.dp,
|
||||||
|
markerSize = 8.dp,
|
||||||
|
markerColor = MaterialTheme.colorScheme.primary)
|
||||||
|
Text(end.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility (!expanded) {
|
||||||
|
Column {
|
||||||
Text(begin.toString(), fontWeight = FontWeight.Bold)
|
Text(begin.toString(), fontWeight = FontWeight.Bold)
|
||||||
Text(end.toString())
|
Text(end.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DividerWithMarker(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
positionPercentage: Float, // от 0f до 1f (например, 0.5f = 50%)
|
||||||
|
color: Color = Color.Gray,
|
||||||
|
thickness: Dp = 1.dp,
|
||||||
|
markerSize: Dp = 8.dp,
|
||||||
|
markerColor: Color = Color.Red
|
||||||
|
) {
|
||||||
|
Canvas(modifier = modifier.height(thickness)) {
|
||||||
|
val dividerHeight = thickness.toPx()
|
||||||
|
val width = size.width
|
||||||
|
val markerX = width * positionPercentage
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
color = markerColor,
|
||||||
|
start = Offset(0f, dividerHeight / 2),
|
||||||
|
end = Offset(markerX, dividerHeight / 2),
|
||||||
|
strokeWidth = dividerHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
drawLine(
|
||||||
|
color = color,
|
||||||
|
start = Offset(markerX, dividerHeight / 2),
|
||||||
|
end = Offset(width, dividerHeight / 2),
|
||||||
|
strokeWidth = dividerHeight / 2
|
||||||
|
)
|
||||||
|
|
||||||
|
drawCircle(
|
||||||
|
color = markerColor,
|
||||||
|
radius = markerSize.toPx() / 2,
|
||||||
|
center = Offset(markerX, dividerHeight / 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun ScheduleItemPreview() {
|
fun ScheduleItemPreview() {
|
||||||
val unit = ScheduleUnit(
|
val unit = ScheduleUnit(
|
||||||
|
oid = 1,
|
||||||
Auditorium(
|
Auditorium(
|
||||||
name = "с/з 1(110)",
|
name = "с/з 1(110)",
|
||||||
oid = 3752,
|
oid = 3752,
|
||||||
@@ -186,8 +367,8 @@ fun ScheduleItemPreview() {
|
|||||||
name = "Фамилия Имя Отчество",
|
name = "Фамилия Имя Отчество",
|
||||||
rank = LecturerRank.SLecturer,
|
rank = LecturerRank.SLecturer,
|
||||||
email = "",
|
email = "",
|
||||||
oid = 28407,
|
unnId = 28000,
|
||||||
uid = "51769"
|
uid = "51000"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
|
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
|
||||||
@@ -196,9 +377,56 @@ fun ScheduleItemPreview() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
UNNTheme {
|
UNNTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
ScheduleItem(unit = unit)
|
ScheduleItem(unit = unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ScheduleExpandedItemPreview() {
|
||||||
|
val unit = ScheduleUnit(
|
||||||
|
oid = 1,
|
||||||
|
Auditorium(
|
||||||
|
name = "с/з 1(110)",
|
||||||
|
oid = 3752,
|
||||||
|
floor = 0,
|
||||||
|
building = Building(
|
||||||
|
name = "Корпус 6",
|
||||||
|
gid = 30,
|
||||||
|
oid = 155
|
||||||
|
),
|
||||||
|
),
|
||||||
|
date = LocalDate.of(2024, 3, 11),
|
||||||
|
discipline = Discipline(
|
||||||
|
name = "Физическая культура и спорт (элективная дисциплина)",
|
||||||
|
oid = 67895,
|
||||||
|
type = 0
|
||||||
|
),
|
||||||
|
kindOfWork = KindOfWork(
|
||||||
|
name = "Практика (семинарские занятия)",
|
||||||
|
oid = 261,
|
||||||
|
uid = "281474976710661",
|
||||||
|
complexity = 1
|
||||||
|
),
|
||||||
|
lecturers = arrayListOf(
|
||||||
|
Lecturer(
|
||||||
|
name = "Фамилия Имя Отчество",
|
||||||
|
rank = LecturerRank.SLecturer,
|
||||||
|
email = "",
|
||||||
|
unnId = 28000,
|
||||||
|
uid = "51000"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
stream = "3823Б1ПР1|3823Б1ПР2|3823Б1ПР3|3823Б1ПР4|3823Б1ПР5-В-OUP",
|
||||||
|
begin = LocalTime.of(10, 50),
|
||||||
|
end = LocalTime.of(12, 20)
|
||||||
|
)
|
||||||
|
|
||||||
|
UNNTheme {
|
||||||
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
|
ScheduleItem(unit = unit, expanded = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/*
|
||||||
|
* Created by sweetbread
|
||||||
|
* Copyright (c) 2025. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
package ru.sweetbread.unn.ui.layout
|
package ru.sweetbread.unn.ui.layout
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -7,19 +12,20 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SnackbarDuration
|
import androidx.compose.material3.SnackbarDuration
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextField
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -36,14 +42,30 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import ru.sweetbread.unn.R
|
import ru.sweetbread.unn.R
|
||||||
import ru.sweetbread.unn.ui.auth
|
import ru.sweetbread.unn.auth
|
||||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
import splitties.activities.start
|
import splitties.activities.start
|
||||||
|
import splitties.preferences.Preferences
|
||||||
|
|
||||||
|
object LoginData : Preferences("loginData") {
|
||||||
|
var login by stringPref("login", "")
|
||||||
|
var password by stringPref("password", "")
|
||||||
|
}
|
||||||
|
|
||||||
class LoginActivity : ComponentActivity() {
|
class LoginActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
if ((LoginData.login != "") and (LoginData.password != ""))
|
||||||
|
runBlocking {
|
||||||
|
if (auth()) {
|
||||||
|
start<MainActivity>()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
UNNTheme {
|
UNNTheme {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -58,20 +80,26 @@ class LoginActivity : ComponentActivity() {
|
|||||||
SnackbarHost(hostState = snackbarHostState)
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
LoginPanel(Modifier.padding(innerPadding), { login, password ->
|
Box(Modifier.padding(innerPadding).fillMaxSize(), Alignment.Center) {
|
||||||
|
LoginPanel(
|
||||||
|
Modifier.imePadding(),
|
||||||
|
{ login, password ->
|
||||||
LoginData.login = login
|
LoginData.login = login
|
||||||
LoginData.password = password
|
LoginData.password = password
|
||||||
start<MainActivity>()
|
start<MainActivity>()
|
||||||
}, {
|
finish()
|
||||||
|
},
|
||||||
|
{
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|
||||||
snackbarHostState
|
snackbarHostState
|
||||||
.showSnackbar(
|
.showSnackbar(
|
||||||
message = "Error",
|
message = "Error",
|
||||||
duration = SnackbarDuration.Long
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,23 +118,24 @@ fun LoginPanel(
|
|||||||
var loading by remember { mutableStateOf(false) }
|
var loading by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Box(Modifier.fillMaxSize(), Alignment.BottomCenter) {
|
|
||||||
Column(
|
Column(
|
||||||
modifier
|
modifier
|
||||||
.padding(32.dp, 0.dp)
|
.padding(32.dp, 0.dp)
|
||||||
.clip(RoundedCornerShape(10.dp, 10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
.padding(16.dp)
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
TextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(8.dp),
|
||||||
value = login,
|
value = login,
|
||||||
onValueChange = { login = it },
|
onValueChange = { login = it },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
label = { Text(stringResource(R.string.prompt_login)) }
|
label = { Text(stringResource(R.string.prompt_login))},
|
||||||
|
placeholder = { Text("s23380101") }
|
||||||
)
|
)
|
||||||
|
|
||||||
TextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.padding(8.dp),
|
modifier = Modifier.padding(8.dp),
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
@@ -116,22 +145,25 @@ fun LoginPanel(
|
|||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
|
||||||
)
|
)
|
||||||
|
|
||||||
Button(modifier = Modifier
|
Button(
|
||||||
.fillMaxWidth()
|
modifier = Modifier.padding(8.dp),
|
||||||
.padding(8.dp), onClick = {
|
enabled = login.trim().isNotEmpty() and password.trim().isNotEmpty() and !loading,
|
||||||
|
onClick = {
|
||||||
loading = true
|
loading = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (auth(login, password)) {
|
if (auth(login, password))
|
||||||
ok(login, password)
|
ok(login, password)
|
||||||
} else {
|
else
|
||||||
error()
|
error()
|
||||||
}
|
|
||||||
loading = false
|
loading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.sign_in))
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
if (loading)
|
||||||
|
CircularProgressIndicator()
|
||||||
|
else
|
||||||
|
Text(stringResource(R.string.sign_in))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,87 +4,44 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AccountBox
|
import androidx.compose.material.icons.filled.AccountBox
|
||||||
import androidx.compose.material.icons.filled.DateRange
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Home
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.zIndex
|
|
||||||
import androidx.navigation.NavType
|
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.room.Room
|
||||||
import com.kizitonwose.calendar.compose.WeekCalendar
|
|
||||||
import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState
|
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.android.Android
|
||||||
import io.ktor.client.plugins.HttpRequestRetry
|
import io.ktor.client.plugins.HttpRequestRetry
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
import io.ktor.client.plugins.cache.HttpCache
|
import io.ktor.client.plugins.cache.HttpCache
|
||||||
import kotlinx.coroutines.Dispatchers
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import kotlinx.coroutines.launch
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import kotlinx.coroutines.runBlocking
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import ru.sweetbread.unn.ui.Auditorium
|
import ru.sweetbread.unn.db.AppDatabase
|
||||||
import ru.sweetbread.unn.ui.Building
|
import ru.sweetbread.unn.ui.composes.Blogposts
|
||||||
import ru.sweetbread.unn.ui.Discipline
|
|
||||||
import ru.sweetbread.unn.ui.KindOfWork
|
|
||||||
import ru.sweetbread.unn.ui.Lecturer
|
|
||||||
import ru.sweetbread.unn.ui.LecturerRank
|
|
||||||
import ru.sweetbread.unn.ui.ScheduleUnit
|
|
||||||
import ru.sweetbread.unn.ui.auth
|
|
||||||
import ru.sweetbread.unn.ui.getSchedule
|
|
||||||
import ru.sweetbread.unn.ui.theme.UNNTheme
|
|
||||||
import splitties.activities.start
|
|
||||||
import splitties.preferences.Preferences
|
|
||||||
import splitties.toast.toast
|
|
||||||
import java.time.DayOfWeek
|
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
import io.ktor.client.plugins.logging.*
|
|
||||||
import ru.sweetbread.unn.ui.composes.Schedule
|
import ru.sweetbread.unn.ui.composes.Schedule
|
||||||
import ru.sweetbread.unn.ui.composes.ScheduleDay
|
import ru.sweetbread.unn.ui.theme.UNNTheme
|
||||||
|
import splitties.arch.room.roomDb
|
||||||
|
import splitties.toast.toast
|
||||||
|
|
||||||
object LoginData : Preferences("loginData") {
|
val client = HttpClient(Android) {
|
||||||
var login by stringPref("login", "")
|
|
||||||
var password by stringPref("password", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
val client = HttpClient {
|
|
||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
install(Logging) {
|
install(Logging) {
|
||||||
logger = object : Logger {
|
logger = object : Logger {
|
||||||
@@ -95,10 +52,10 @@ val client = HttpClient {
|
|||||||
level = LogLevel.ALL
|
level = LogLevel.ALL
|
||||||
}
|
}
|
||||||
install(HttpTimeout) {
|
install(HttpTimeout) {
|
||||||
socketTimeoutMillis = HttpTimeout.INFINITE_TIMEOUT_MS
|
connectTimeoutMillis = 5000
|
||||||
}
|
}
|
||||||
install(HttpRequestRetry) {
|
install(HttpRequestRetry) {
|
||||||
retryOnServerErrors(maxRetries = 3)
|
retryOnException(maxRetries = 3, retryOnTimeout = true)
|
||||||
exponentialDelay()
|
exponentialDelay()
|
||||||
modifyRequest { request ->
|
modifyRequest { request ->
|
||||||
request.headers.append("x-retry-count", retryCount.toString())
|
request.headers.append("x-retry-count", retryCount.toString())
|
||||||
@@ -106,46 +63,50 @@ val client = HttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val db = roomDb<AppDatabase>(name = "database")
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
|
||||||
if (LoginData.login.isEmpty() or LoginData.password.isEmpty()) start<LoginActivity>()
|
|
||||||
runBlocking {
|
|
||||||
if (!auth()) start<LoginActivity>()
|
|
||||||
}
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
UNNTheme {
|
UNNTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
var route by remember { mutableStateOf("portal/blogposts") }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
onClick = { toast("Not implemented") },
|
onClick = {
|
||||||
|
route = "portal/blogposts"
|
||||||
|
navController.navigate(route)
|
||||||
|
},
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Home,
|
Icons.Filled.Home,
|
||||||
contentDescription = "Home"
|
contentDescription = "Home"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selected = navController.currentDestination?.route?.startsWith("home") ?: false
|
selected = route.startsWith("portal/")
|
||||||
)
|
)
|
||||||
|
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
onClick = { navController.navigate("schedule/student/me/today")
|
onClick = {
|
||||||
Log.d("route", navController.currentDestination?.route.toString())},
|
route = "journal/schedule"
|
||||||
|
navController.navigate(route)
|
||||||
|
},
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.DateRange,
|
Icons.Filled.DateRange,
|
||||||
contentDescription = "Schedule"
|
contentDescription = "Schedule"
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
selected = navController.currentDestination?.route?.startsWith("schedule") ?: false
|
selected = route.startsWith("journal/")
|
||||||
)
|
)
|
||||||
|
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
onClick = { toast("Not implemented") },
|
onClick = { toast("Not implemented") },
|
||||||
icon = {
|
icon = {
|
||||||
@@ -158,19 +119,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
) {innerPadding ->
|
) {innerPadding ->
|
||||||
Box(Modifier.padding(innerPadding)) {
|
Box(Modifier.padding(innerPadding)) {
|
||||||
NavHost(navController, startDestination = "home/blogposts") {
|
NavHost(navController, startDestination = "portal/blogposts") {
|
||||||
composable("home/blogposts") {
|
composable("portal/blogposts") {
|
||||||
Text("Not implemented")
|
Blogposts()
|
||||||
}
|
}
|
||||||
composable("schedule/{type}/{who}/{when}",
|
composable("journal/schedule") {
|
||||||
arguments = listOf(
|
|
||||||
navArgument("type") { type = NavType.StringType },
|
|
||||||
navArgument("who") { type = NavType.StringType },
|
|
||||||
navArgument("when") { type = NavType.StringType },)
|
|
||||||
) {
|
|
||||||
Schedule()
|
Schedule()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#3DDC84"
|
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M9,0L9,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,0L19,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,0L29,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,0L39,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,0L49,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,0L59,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,0L69,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,0L79,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M89,0L89,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M99,0L99,108"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,9L108,9"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,19L108,19"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,29L108,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,39L108,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,49L108,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,59L108,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,69L108,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,79L108,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,89L108,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M0,99L108,99"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,29L89,29"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,39L89,39"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,49L89,49"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,59L89,59"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,69L89,69"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M19,79L89,79"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M29,19L29,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M39,19L39,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M49,19L49,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M59,19L59,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M69,19L69,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:pathData="M79,19L79,89"
|
|
||||||
android:strokeWidth="0.8"
|
|
||||||
android:strokeColor="#33FFFFFF" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:endX="85.84757"
|
|
||||||
android:endY="92.4963"
|
|
||||||
android:startX="42.9492"
|
|
||||||
android:startY="49.59793"
|
|
||||||
android:type="linear">
|
|
||||||
<item
|
|
||||||
android:color="#44000000"
|
|
||||||
android:offset="0.0" />
|
|
||||||
<item
|
|
||||||
android:color="#00000000"
|
|
||||||
android:offset="1.0" />
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFF"
|
|
||||||
android:fillType="nonZero"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
||||||
android:strokeWidth="1"
|
|
||||||
android:strokeColor="#00000000" />
|
|
||||||
</vector>
|
|
||||||
|
Before Width: | Height: | Size: 76 KiB |
@@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 878 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 534 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.1 KiB |
@@ -1,6 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<string name="app_name_reg">ННГУ</string>
|
||||||
|
<string name="app_name_dev">ННГУ Альфа</string>
|
||||||
|
<string name="app_name_beta">ННГУ Бета</string>
|
||||||
<string name="prompt_password">Пароль</string>
|
<string name="prompt_password">Пароль</string>
|
||||||
<string name="prompt_login">Логин</string>
|
<string name="prompt_login">Логин</string>
|
||||||
<string name="sign_in">Войти</string>
|
<string name="sign_in">Войти</string>
|
||||||
|
<string name="assistant">Ассистент</string>
|
||||||
|
<string name="lecturer">Преподаватель</string>
|
||||||
|
<string name="slecturer">Старший Преподаватель</string>
|
||||||
|
<string name="aprofessor">Доцент</string>
|
||||||
|
<string name="auditorium">Аудитория</string>
|
||||||
|
<string name="building">Здание</string>
|
||||||
|
<string name="floor">Этаж</string>
|
||||||
|
<string name="noData">Нет данных</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#FFFFFF</color>
|
<color name="ic_launcher_background">#1565AA</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,9 +1,20 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">UNN</string>
|
<string name="app_name_reg">UNN</string>
|
||||||
<!-- <string name="title_activity_login">LoginActivity</string>-->
|
<string name="app_name_dev">UNN Dev</string>
|
||||||
|
<string name="app_name_beta">UNN Beta</string>
|
||||||
|
<!-- <string name="title_activity_login">LoginActivity</string>-->
|
||||||
<string name="prompt_email" translatable="false">Email</string>
|
<string name="prompt_email" translatable="false">Email</string>
|
||||||
<string name="prompt_login">Login</string>
|
<string name="prompt_login">Login</string>
|
||||||
<string name="prompt_password">Password</string>
|
<string name="prompt_password">Password</string>
|
||||||
<string name="sign_in">Sign in</string>
|
<string name="sign_in">Sign in</string>
|
||||||
|
<string name="assistant">Assistant</string>
|
||||||
|
<string name="lecturer">Lecturer</string>
|
||||||
|
<string name="slecturer">Senior Lecturer</string>
|
||||||
|
<string name="aprofessor">Assistant professor</string>
|
||||||
|
<string name="auditorium">Auditorium</string>
|
||||||
|
<string name="building">Building</string>
|
||||||
|
<string name="floor">Floor</string>
|
||||||
|
<string name="lorem" translatable="false">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vel iaculis elit. Aliquam varius urna ut nisl rhoncus ullamcorper. Maecenas et nisl at dui mollis maximus nec in libero. Ut eu nulla id felis hendrerit lobortis. Maecenas vel facilisis lectus. Morbi eleifend massa a ante consequat, eu aliquam elit euismod. Aenean quis erat tincidunt, egestas ligula id, convallis tortor. Vivamus volutpat condimentum nisl sed eleifend. Aenean dapibus dolor ut orci lobortis, placerat lobortis tortor pretium. Nam eros lectus, convallis sed ultricies sit amet, lacinia sed sem. In mi odio, porta non malesuada et, cursus a metus. Morbi quis odio sed quam commodo gravida id sit amet dolor. Donec ac iaculis massa. Nulla mauris sapien, auctor consequat est in, tempus accumsan ipsum. Donec semper volutpat nisi. Quisque dignissim tellus ipsum, sed malesuada libero aliquam sed. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nam eleifend pharetra orci eu scelerisque. In hac habitasse platea dictumst. Sed non neque vitae metus porttitor vestibulum ut eget felis. Aliquam venenatis a magna eu mattis. Proin rutrum, sapien id viverra finibus, nisi quam aliquam eros, et dignissim lectus sem sit amet purus. Donec et semper enim, sed pretium lacus. Nullam venenatis ullamcorper maximus. Mauris pellentesque velit non sem sollicitudin molestie. Duis hendrerit consequat enim eget euismod.</string>
|
||||||
|
<string name="noData">No Data</string>
|
||||||
<!-- <string name="login_failed">"Login failed"</string>-->
|
<!-- <string name="login_failed">"Login failed"</string>-->
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.UNN" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.UNN" parent="android:Theme.Material.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Sample data extraction rules file; uncomment and customize as necessary.
|
|
||||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
|
||||||
for details.
|
|
||||||
-->
|
|
||||||
<data-extraction-rules>
|
|
||||||
<cloud-backup>
|
|
||||||
<!--
|
|
||||||
TODO: Use <include> and <exclude> to control what is backed up.
|
|
||||||
The domain can be file, database, sharedpref, external or root.
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
<include domain="file" path="file_to_include"/>
|
|
||||||
<exclude domain="file" path="file_to_exclude"/>
|
|
||||||
<include domain="file" path="include_folder"/>
|
|
||||||
<exclude domain="file" path="include_folder/file_to_exclude"/>
|
|
||||||
<exclude domain="file" path="exclude_folder"/>
|
|
||||||
<include domain="file" path="exclude_folder/file_to_include"/>
|
|
||||||
|
|
||||||
<include domain="sharedpref" path="include_shared_pref1.xml"/>
|
|
||||||
<include domain="database" path="db_name/file_to_include"/>
|
|
||||||
<exclude domain="database" path="db_name/include_folder/file_to_exclude"/>
|
|
||||||
<include domain="external" path="file_to_include"/>
|
|
||||||
<exclude domain="external" path="file_to_exclude"/>
|
|
||||||
<include domain="root" path="file_to_include"/>
|
|
||||||
<exclude domain="root" path="file_to_exclude"/>
|
|
||||||
-->
|
|
||||||
</cloud-backup>
|
|
||||||
<!--
|
|
||||||
<device-transfer>
|
|
||||||
<include .../>
|
|
||||||
<exclude .../>
|
|
||||||
</device-transfer>
|
|
||||||
-->
|
|
||||||
</data-extraction-rules>
|
|
||||||
@@ -2,4 +2,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.androidApplication) apply false
|
alias(libs.plugins.androidApplication) apply false
|
||||||
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
|
||||||
|
id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath(libs.secrets.gradle.plugin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,55 +1,70 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.3.0"
|
acraHttp = "5.11.3"
|
||||||
datastorePreferences = "1.0.0"
|
agp = "8.7.0"
|
||||||
|
calendar = "2.5.4"
|
||||||
|
coilCompose = "2.7.0"
|
||||||
|
compose = "1.6.4" # Updating this will cause an error!
|
||||||
|
coreSplashscreen = "1.0.1"
|
||||||
|
datastorePreferences = "1.1.1"
|
||||||
|
desugar_jdk_libs = "2.1.2"
|
||||||
kotlin = "1.9.0"
|
kotlin = "1.9.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.13.1"
|
||||||
junit = "4.13.2"
|
junitVersion = "1.2.1"
|
||||||
junitVersion = "1.1.5"
|
espressoCore = "3.6.1"
|
||||||
espressoCore = "3.5.1"
|
ktor = "2.3.12"
|
||||||
ktorClientCio = "2.3.9"
|
lifecycle = "2.8.5"
|
||||||
ktorClientCore = "2.3.9"
|
activityCompose = "1.9.2"
|
||||||
ktorClientLogging = "2.3.9"
|
composeBom = "2024.03.00" # Updating this will cause an error!
|
||||||
lifecycleRuntimeKtx = "2.6.1"
|
appcompat = "1.7.0"
|
||||||
activityCompose = "1.7.0"
|
material = "1.12.0"
|
||||||
composeBom = "2023.08.00"
|
annotation = "1.8.2"
|
||||||
appcompat = "1.6.1"
|
|
||||||
material = "1.10.0"
|
|
||||||
annotation = "1.6.0"
|
|
||||||
constraintlayout = "2.1.4"
|
constraintlayout = "2.1.4"
|
||||||
lifecycleLivedataKtx = "2.6.1"
|
activity = "1.9.2"
|
||||||
lifecycleViewmodelKtx = "2.6.1"
|
navigationCompose = "2.7.7" # Updating this will cause an error!
|
||||||
activity = "1.8.0"
|
roomRuntime = "2.6.1"
|
||||||
navigationCompose = "2.7.7"
|
secretsGradlePlugin = "2.0.1"
|
||||||
splittiesFunPackAndroidBaseWithViewsDsl = "3.0.0"
|
splitties = "3.0.0"
|
||||||
|
kefirbb = "1.5"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }
|
||||||
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomRuntime" }
|
||||||
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
|
||||||
|
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
|
||||||
|
compose = { module = "com.kizitonwose.calendar:compose", version.ref = "calendar" }
|
||||||
|
desugar_jdk_libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar_jdk_libs" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "compose" }
|
||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCio" }
|
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
|
||||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" }
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||||
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktorClientLogging" }
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
|
ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
|
androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
|
||||||
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||||
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }
|
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
|
||||||
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
|
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
|
||||||
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||||
splitties-fun-pack-android-base-with-views-dsl = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splittiesFunPackAndroidBaseWithViewsDsl" }
|
secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }
|
||||||
|
splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splitties" }
|
||||||
|
splitties-room = { module = "com.louiscad.splitties:splitties-arch-room", version.ref = "splitties" }
|
||||||
|
kefirbb = { group = "org.kefirsf", name = "kefirbb", version.ref = "kefirbb" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Sat Mar 16 18:30:45 MSK 2024
|
#Sat Mar 16 18:30:45 MSK 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||