mirror of
https://github.com/StepanovPlaton/SSAU_Schedule.git
synced 2026-04-03 12:20:39 +04:00
Complete work
This commit is contained in:
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,6 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
<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" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ android {
|
|||||||
composeOptions {
|
composeOptions {
|
||||||
kotlinCompilerExtensionVersion = "1.5.1"
|
kotlinCompilerExtensionVersion = "1.5.1"
|
||||||
}
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
@@ -114,4 +117,7 @@ dependencies {
|
|||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
|
|
||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
|
|
||||||
|
implementation(libs.androidx.glance)
|
||||||
|
implementation(libs.androidx.glance.material)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
{
|
|
||||||
"formatVersion": 1,
|
|
||||||
"database": {
|
|
||||||
"version": 1,
|
|
||||||
"identityHash": "d3086e45becf402ec027d32056b910a8",
|
|
||||||
"entities": [
|
|
||||||
{
|
|
||||||
"tableName": "lessons",
|
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` TEXT, `discipline` TEXT NOT NULL, `week` INTEGER NOT NULL, `day_of_week` INTEGER NOT NULL, `teacher` TEXT NOT NULL, `begin_time` TEXT NOT NULL, `end_time` TEXT NOT NULL, `conference_url` TEXT, PRIMARY KEY(`id`))",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldPath": "id",
|
|
||||||
"columnName": "id",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "type",
|
|
||||||
"columnName": "type",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "discipline",
|
|
||||||
"columnName": "discipline",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "week",
|
|
||||||
"columnName": "week",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "dayOfWeek",
|
|
||||||
"columnName": "day_of_week",
|
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "teacher",
|
|
||||||
"columnName": "teacher",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "beginTime",
|
|
||||||
"columnName": "begin_time",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "endTime",
|
|
||||||
"columnName": "end_time",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "conferenceUrl",
|
|
||||||
"columnName": "conference_url",
|
|
||||||
"affinity": "TEXT",
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"primaryKey": {
|
|
||||||
"autoGenerate": false,
|
|
||||||
"columnNames": [
|
|
||||||
"id"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd3086e45becf402ec027d32056b910a8')"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -32,5 +32,16 @@
|
|||||||
android:theme="@style/Theme.SSAU_Schedule"
|
android:theme="@style/Theme.SSAU_Schedule"
|
||||||
android:screenOrientation="portrait">
|
android:screenOrientation="portrait">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".widget.WidgetReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_info" />
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -110,7 +110,7 @@ class AuthActivity : ComponentActivity() {
|
|||||||
var entered by remember { mutableStateOf(false) }
|
var entered by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val keyboardOpen by Utils.keyboardState()
|
val keyboardOpen by Utils.keyboardState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val notificationState = remember { SnackbarHostState() }
|
||||||
val logoHeight by animateFloatAsState(
|
val logoHeight by animateFloatAsState(
|
||||||
if (keyboardOpen && needAuth) 0f else min(
|
if (keyboardOpen && needAuth) 0f else min(
|
||||||
LocalConfiguration.current.screenWidthDp,
|
LocalConfiguration.current.screenWidthDp,
|
||||||
@@ -124,7 +124,7 @@ class AuthActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(user, group, year) {
|
LaunchedEffect(user, group, year) {
|
||||||
if(user != null && group != null && year != null) {
|
if (user != null && group != null && year != null) {
|
||||||
delay(2500)
|
delay(2500)
|
||||||
startActivity(Intent(applicationContext, MainActivity::class.java))
|
startActivity(Intent(applicationContext, MainActivity::class.java))
|
||||||
}
|
}
|
||||||
@@ -134,22 +134,30 @@ class AuthActivity : ComponentActivity() {
|
|||||||
delay(3000)
|
delay(3000)
|
||||||
|
|
||||||
val token = AuthStore.getAuthToken(applicationContext)
|
val token = AuthStore.getAuthToken(applicationContext)
|
||||||
if(token == null) { needAuth = true; return@LaunchedEffect }
|
if (token == null) {
|
||||||
|
needAuth = true; return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
val (userDetails) = userAPI.getUserDetails(token)
|
val (userDetails) = userAPI.getUserDetails(token)
|
||||||
if(userDetails == null) { needAuth = true; return@LaunchedEffect }
|
if (userDetails == null) {
|
||||||
else { user = userDetails }
|
needAuth = true; return@LaunchedEffect
|
||||||
|
} else {
|
||||||
|
user = userDetails
|
||||||
|
}
|
||||||
|
|
||||||
val (groups, groupsError) = groupAPI.getUserGroups(token)
|
val (groups, groupsError) = groupAPI.getUserGroups(token)
|
||||||
if(groups == null) {
|
if (groups == null) {
|
||||||
if(groupsError != null && groupsError !=
|
if (groupsError != null && groupsError !=
|
||||||
GroupAPIErrorMessage.USER_NOT_AUTHORIZED) {
|
GroupAPIErrorMessage.USER_NOT_AUTHORIZED
|
||||||
|
) {
|
||||||
val message = groupsError.getMessage(applicationContext)
|
val message = groupsError.getMessage(applicationContext)
|
||||||
if(message != null) snackbarHostState.showSnackbar(message)
|
if (message != null) notificationState.showSnackbar(message)
|
||||||
} else { needAuth = true; return@LaunchedEffect }
|
} else {
|
||||||
|
needAuth = true; return@LaunchedEffect
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val currentGroup = GroupStore.getCurrentGroup(applicationContext)
|
val currentGroup = GroupStore.getCurrentGroup(applicationContext)
|
||||||
if(currentGroup != null && groups.contains(currentGroup)) group = currentGroup
|
if (currentGroup != null && groups.contains(currentGroup)) group = currentGroup
|
||||||
else {
|
else {
|
||||||
GroupStore.setCurrentGroup(groups[0], applicationContext)
|
GroupStore.setCurrentGroup(groups[0], applicationContext)
|
||||||
group = groups[0]
|
group = groups[0]
|
||||||
@@ -157,28 +165,31 @@ class AuthActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val (years, yearsError) = yearAPI.getYears(token)
|
val (years, yearsError) = yearAPI.getYears(token)
|
||||||
if(years == null) {
|
if (years == null) {
|
||||||
if(yearsError != null && yearsError !=
|
if (yearsError != null && yearsError !=
|
||||||
YearAPIErrorMessage.USER_NOT_AUTHORIZED) {
|
YearAPIErrorMessage.USER_NOT_AUTHORIZED
|
||||||
|
) {
|
||||||
val message = yearsError.getMessage(applicationContext)
|
val message = yearsError.getMessage(applicationContext)
|
||||||
if(message != null) snackbarHostState.showSnackbar(message)
|
if (message != null) notificationState.showSnackbar(message)
|
||||||
} else { needAuth = true; return@LaunchedEffect }
|
} else {
|
||||||
|
needAuth = true; return@LaunchedEffect
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val currentRawYear = years.find { y -> y.isCurrent }
|
val currentRawYear = years.find { y -> y.isCurrent }
|
||||||
if(currentRawYear != null) {
|
if (currentRawYear != null) {
|
||||||
year = currentRawYear.toYear()
|
year = currentRawYear.toYear()
|
||||||
YearStore.setCurrentYear(year!!, applicationContext, authScope)
|
YearStore.setCurrentYear(year!!, applicationContext, authScope)
|
||||||
} else {
|
} else {
|
||||||
val message = YearAPIErrorMessage.FAILED_GET_YEARS
|
val message = YearAPIErrorMessage.FAILED_GET_YEARS
|
||||||
.getMessage(applicationContext)
|
.getMessage(applicationContext)
|
||||||
if(message != null) snackbarHostState.showSnackbar(message)
|
if (message != null) notificationState.showSnackbar(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(hostState = snackbarHostState) {
|
SnackbarHost(hostState = notificationState) {
|
||||||
Snackbar(
|
Snackbar(
|
||||||
snackbarData = it,
|
snackbarData = it,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
@@ -187,8 +198,12 @@ class AuthActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Box(Modifier.background(MaterialTheme.colorScheme.primary)
|
Box(
|
||||||
.fillMaxSize().padding(padding).imePadding(),
|
Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.primary)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding)
|
||||||
|
.imePadding(),
|
||||||
contentAlignment = BiasAlignment(0f, -0.25f),
|
contentAlignment = BiasAlignment(0f, -0.25f),
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
@@ -196,19 +211,23 @@ class AuthActivity : ComponentActivity() {
|
|||||||
Modifier
|
Modifier
|
||||||
.widthIn(0.dp, 500.dp)
|
.widthIn(0.dp, 500.dp)
|
||||||
.height(logoHeight.dp)
|
.height(logoHeight.dp)
|
||||||
.padding(20.dp, 0.dp)) {
|
.padding(20.dp, 0.dp)
|
||||||
Image(painterResource(R.drawable.ssau_logo_01),
|
) {
|
||||||
|
Image(
|
||||||
|
painterResource(R.drawable.ssau_logo_01),
|
||||||
contentDescription = stringResource(R.string.samara_university),
|
contentDescription = stringResource(R.string.samara_university),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(10.dp),
|
.padding(10.dp),
|
||||||
contentScale = ContentScale.FillWidth,
|
contentScale = ContentScale.FillWidth,
|
||||||
alignment = Alignment.TopCenter)
|
alignment = Alignment.TopCenter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.padding(20.dp, 0.dp)
|
.padding(20.dp, 0.dp)
|
||||||
.widthIn(0.dp, 400.dp)) {
|
.widthIn(0.dp, 400.dp)
|
||||||
|
) {
|
||||||
Column {
|
Column {
|
||||||
WelcomeMessage(user, group, year)
|
WelcomeMessage(user, group, year)
|
||||||
AuthForm(open = needAuth, authScope) {
|
AuthForm(open = needAuth, authScope) {
|
||||||
@@ -230,26 +249,37 @@ class AuthActivity : ComponentActivity() {
|
|||||||
var password by remember { mutableStateOf("") }
|
var password by remember { mutableStateOf("") }
|
||||||
var error by remember { mutableStateOf<AuthErrorMessage?>(null) }
|
var error by remember { mutableStateOf<AuthErrorMessage?>(null) }
|
||||||
|
|
||||||
val height by animateDpAsState(if (open) 290.dp else 0.dp, label = "Auth form height",
|
val height by animateDpAsState(
|
||||||
|
if (open) 290.dp else 0.dp, label = "Auth form height",
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioLowBouncy,
|
dampingRatio = Spring.DampingRatioLowBouncy,
|
||||||
stiffness = Spring.StiffnessLow
|
stiffness = Spring.StiffnessLow
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
Card(Modifier.fillMaxWidth().height(height).padding(0.dp, 10.dp).shadow(10.dp),
|
Card(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(height)
|
||||||
|
.padding(0.dp, 10.dp)
|
||||||
|
.shadow(10.dp),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxWidth().padding(30.dp, 20.dp),
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(30.dp, 20.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.sign_in),
|
Text(
|
||||||
|
stringResource(R.string.sign_in),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
style = MaterialTheme.typography.displaySmall)
|
style = MaterialTheme.typography.displaySmall
|
||||||
|
)
|
||||||
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
|
OutlinedTextField(modifier = Modifier.fillMaxWidth(),
|
||||||
value = login,
|
value = login,
|
||||||
onValueChange = { login = it; error = null },
|
onValueChange = { login = it; error = null },
|
||||||
@@ -262,14 +292,19 @@ class AuthActivity : ComponentActivity() {
|
|||||||
label = { Text(stringResource(R.string.password)) },
|
label = { Text(stringResource(R.string.password)) },
|
||||||
placeholder = { Text(stringResource(R.string.enter_your_password)) })
|
placeholder = { Text(stringResource(R.string.enter_your_password)) })
|
||||||
Spacer(Modifier.height(2.dp))
|
Spacer(Modifier.height(2.dp))
|
||||||
Box(Modifier.fillMaxWidth().height(14.dp)) {
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(14.dp)) {
|
||||||
this@Column.AnimatedVisibility(
|
this@Column.AnimatedVisibility(
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
visible = error !== null
|
visible = error !== null
|
||||||
) {
|
) {
|
||||||
Text(error?.getMessage(applicationContext) ?: "",
|
Text(
|
||||||
|
error?.getMessage(applicationContext) ?: "",
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.labelSmall)
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
@@ -279,11 +314,10 @@ class AuthActivity : ComponentActivity() {
|
|||||||
else if (password.length < 5) error = AuthErrorMessage.PASSWORD_IS_TOO_SHORT
|
else if (password.length < 5) error = AuthErrorMessage.PASSWORD_IS_TOO_SHORT
|
||||||
else scope.launch {
|
else scope.launch {
|
||||||
val (token) = authAPI.signIn(login, password)
|
val (token) = authAPI.signIn(login, password)
|
||||||
if(token != null) {
|
if (token != null) {
|
||||||
AuthStore.setAuthToken(token, applicationContext)
|
AuthStore.setAuthToken(token, applicationContext)
|
||||||
callback()
|
callback()
|
||||||
}
|
} else error = AuthErrorMessage.INCORRECT_LOGIN_OR_PASSWORD
|
||||||
else error = AuthErrorMessage.INCORRECT_LOGIN_OR_PASSWORD
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(50),
|
shape = RoundedCornerShape(50),
|
||||||
@@ -298,26 +332,35 @@ class AuthActivity : ComponentActivity() {
|
|||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
@Composable
|
@Composable
|
||||||
fun WelcomeMessage(user: User?, group: Group?, year: Year?) {
|
fun WelcomeMessage(user: User?, group: Group?, year: Year?) {
|
||||||
val currentDate = remember { SimpleDateFormat("d MMMM").format(Date()) }
|
val currentDate = remember { SimpleDateFormat("d MMMM").format(Date()) }
|
||||||
val currentYear = remember { Calendar.getInstance().get(Calendar.YEAR); }
|
val currentYear = remember { Calendar.getInstance().get(Calendar.YEAR); }
|
||||||
Column(Modifier.fillMaxWidth().animateContentSize(),
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateContentSize(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if(user !== null && group != null && year != null) {
|
if (user !== null && group != null && year != null) {
|
||||||
Text("${stringResource(R.string.hello)} ${user.name}!",
|
Text(
|
||||||
|
"${stringResource(R.string.hello)} ${user.name}!",
|
||||||
color = ApplicationColors.White,
|
color = ApplicationColors.White,
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center
|
||||||
Text("${stringResource(R.string.schedule_for_group)} ${group.name}",
|
)
|
||||||
|
Text(
|
||||||
|
"${stringResource(R.string.schedule_for_group)} ${group.name}",
|
||||||
color = ApplicationColors.White,
|
color = ApplicationColors.White,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center
|
||||||
Text("$currentDate, ${year.getWeekOfDate(Date())} "+
|
)
|
||||||
"${stringResource(R.string.education_week)}, ${currentYear}-"+
|
Text(
|
||||||
"${currentYear+1} ${stringResource(R.string.education_year)}",
|
"$currentDate, ${year.getWeekOfDate(Date())} " +
|
||||||
|
"${stringResource(R.string.education_week)}, ${currentYear}-" +
|
||||||
|
"${currentYear + 1} ${stringResource(R.string.education_year)}",
|
||||||
color = ApplicationColors.White,
|
color = ApplicationColors.White,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.example.ssau_schedule
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
@@ -36,18 +35,18 @@ import androidx.compose.material3.SnackbarHostState
|
|||||||
import androidx.compose.material3.Text
|
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.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
|
||||||
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.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.glance.appwidget.updateAll
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
import com.example.ssau_schedule.api.Http
|
import com.example.ssau_schedule.api.Http
|
||||||
import com.example.ssau_schedule.api.LessonAPI
|
import com.example.ssau_schedule.api.LessonAPI
|
||||||
import com.example.ssau_schedule.api.LessonAPIErrorMessage
|
import com.example.ssau_schedule.api.LessonAPIErrorMessage
|
||||||
@@ -55,18 +54,24 @@ import com.example.ssau_schedule.components.EmptyDay
|
|||||||
import com.example.ssau_schedule.components.LessonCards
|
import com.example.ssau_schedule.components.LessonCards
|
||||||
import com.example.ssau_schedule.data.base.Database
|
import com.example.ssau_schedule.data.base.Database
|
||||||
import com.example.ssau_schedule.data.base.entity.lesson.Lesson
|
import com.example.ssau_schedule.data.base.entity.lesson.Lesson
|
||||||
|
import com.example.ssau_schedule.data.store.GeneralData
|
||||||
import com.example.ssau_schedule.data.store.StoreUtils
|
import com.example.ssau_schedule.data.store.StoreUtils
|
||||||
|
import com.example.ssau_schedule.data.store.Year
|
||||||
import com.example.ssau_schedule.ui.theme.SSAU_ScheduleTheme
|
import com.example.ssau_schedule.ui.theme.SSAU_ScheduleTheme
|
||||||
|
import com.example.ssau_schedule.widget.ScheduleWidget
|
||||||
|
import com.example.ssau_schedule.work.RequestLessonsWorker
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val http = Http()
|
private val http = Http()
|
||||||
private val lessonAPI = LessonAPI(http)
|
private val lessonAPI = LessonAPI(http)
|
||||||
private lateinit var database: Database
|
private lateinit var database: Database
|
||||||
|
|
||||||
|
private lateinit var workManager: WorkManager
|
||||||
|
private val workName = "SSAUSchedule"
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
@@ -81,51 +86,73 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun MainPage() {
|
fun MainPage() {
|
||||||
database = remember { Database.getInstance(applicationContext) }
|
database = remember { Database.getInstance(applicationContext) }
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
workManager = remember { WorkManager.getInstance(applicationContext) }
|
||||||
|
val notificationState = remember { SnackbarHostState() }
|
||||||
val lessons = remember { mutableStateOf<List<Lesson>>(listOf()) }
|
val lessons = remember { mutableStateOf<List<Lesson>>(listOf()) }
|
||||||
val animationScope = rememberCoroutineScope()
|
val animationScope = rememberCoroutineScope()
|
||||||
val currentDate = remember { mutableStateOf(Date()) }
|
|
||||||
val currentDayOfWeek = remember {
|
|
||||||
mutableIntStateOf(Utils.Date.getDayOfWeek(currentDate.value))
|
|
||||||
}
|
|
||||||
val pagerState = rememberPagerState(
|
val pagerState = rememberPagerState(
|
||||||
initialPage = currentDayOfWeek.intValue-1, pageCount = {Int.MAX_VALUE})
|
initialPage = Utils.Date.getDayOfWeek(Date()) - 1, pageCount = { Int.MAX_VALUE })
|
||||||
|
val studyYear = remember { mutableStateOf<Year?>(null) }
|
||||||
|
val loadedWeeks = remember { mutableStateOf<List<Int>>(listOf()) }
|
||||||
|
val workStarted = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// LaunchedEffect(false) {
|
suspend fun getLessons(generalData: GeneralData, week: Int) {
|
||||||
// lessons.value = database.lessonDao().getAll()
|
val (apiLessons, apiError) = lessonAPI.getLessons(
|
||||||
// }
|
generalData.token, generalData.group, generalData.year, week
|
||||||
|
)
|
||||||
|
if (apiLessons != null && apiError == null) {
|
||||||
|
val (databaseLessons, converterErrors) = apiLessons.toLessons(week)
|
||||||
|
database.lessonDao().insert(*databaseLessons.toTypedArray())
|
||||||
|
converterErrors.forEach { error ->
|
||||||
|
val message = error.getMessage(applicationContext)
|
||||||
|
if (message != null) notificationState.showSnackbar(message)
|
||||||
|
}
|
||||||
|
lessons.value = lessons.value.plus(databaseLessons)
|
||||||
|
loadedWeeks.value = loadedWeeks.value.plus(week)
|
||||||
|
ScheduleWidget().updateAll(applicationContext)
|
||||||
|
} else {
|
||||||
|
if (apiError == LessonAPIErrorMessage.USER_NOT_AUTHORIZED) {
|
||||||
|
startActivity(Intent(applicationContext, AuthActivity::class.java))
|
||||||
|
} else {
|
||||||
|
val message = apiError?.getMessage(applicationContext)
|
||||||
|
if (message != null) notificationState.showSnackbar(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(false) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
val generalData = StoreUtils.getGeneralData(applicationContext)
|
val generalData = StoreUtils.getGeneralData(applicationContext)
|
||||||
if(generalData == null)
|
if (generalData == null)
|
||||||
startActivity(Intent(applicationContext, AuthActivity::class.java))
|
startActivity(Intent(applicationContext, AuthActivity::class.java))
|
||||||
else {
|
else {
|
||||||
val week = generalData.year.getWeekOfDate(Date())
|
if (!workStarted.value) {
|
||||||
val (apiLessons, apiError) = lessonAPI.getLessons(
|
val workRequest = PeriodicWorkRequestBuilder<RequestLessonsWorker>(
|
||||||
generalData.token, generalData.group, generalData.year, week)
|
repeatInterval = 3,
|
||||||
if(apiLessons != null && apiError == null) {
|
TimeUnit.HOURS
|
||||||
val (databaseLessons, converterErrors) = apiLessons.toLessons(week)
|
).setInitialDelay(1, TimeUnit.HOURS).build()
|
||||||
Log.i("Lessons", Json.encodeToString(apiLessons))
|
workManager.enqueueUniquePeriodicWork(
|
||||||
database.lessonDao().insert(*databaseLessons.toTypedArray())
|
workName,
|
||||||
converterErrors.forEach { error ->
|
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||||
val message = error.getMessage(applicationContext)
|
workRequest
|
||||||
if(message != null) snackbarHostState.showSnackbar(message)
|
)
|
||||||
}
|
workStarted.value = true
|
||||||
lessons.value = databaseLessons
|
|
||||||
} else {
|
|
||||||
if(apiError == LessonAPIErrorMessage.USER_NOT_AUTHORIZED) {
|
|
||||||
startActivity(Intent(applicationContext, AuthActivity::class.java))
|
|
||||||
} else {
|
|
||||||
val message = apiError?.getMessage(applicationContext)
|
|
||||||
if(message != null) snackbarHostState.showSnackbar(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
studyYear.value = generalData.year
|
||||||
|
val day = Utils.Date.addDays(
|
||||||
|
Date(),
|
||||||
|
pagerState.currentPage - Utils.Date.getDayOfWeek(Date())+1
|
||||||
|
)
|
||||||
|
var week = generalData.year.getWeekOfDate(day)
|
||||||
|
if (!loadedWeeks.value.contains(week)) getLessons(generalData, week)
|
||||||
|
if (Utils.Date.getDayOfWeek(day) == 6) week++
|
||||||
|
if (!loadedWeeks.value.contains(week)) getLessons(generalData, week)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(hostState = snackbarHostState) {
|
SnackbarHost(hostState = notificationState) {
|
||||||
Snackbar(
|
Snackbar(
|
||||||
snackbarData = it,
|
snackbarData = it,
|
||||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
@@ -142,68 +169,102 @@ class MainActivity : ComponentActivity() {
|
|||||||
.imePadding(),
|
.imePadding(),
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Box(Modifier.fillMaxWidth().height(60.dp)) {
|
Box(
|
||||||
Row(Modifier.fillMaxSize().padding(10.dp),
|
Modifier
|
||||||
horizontalArrangement = Arrangement.SpaceBetween) {
|
.fillMaxWidth()
|
||||||
Box(Modifier.height(40.dp).width(40.dp)
|
.height(60.dp)) {
|
||||||
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
Row(
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
Modifier
|
||||||
.clickable {
|
.fillMaxSize()
|
||||||
animationScope.launch {
|
.padding(10.dp),
|
||||||
pagerState.animateScrollToPage(pagerState.currentPage-1)
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
}
|
) {
|
||||||
},
|
Box(
|
||||||
|
Modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.width(40.dp)
|
||||||
|
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
||||||
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
|
.clickable {
|
||||||
|
animationScope.launch {
|
||||||
|
pagerState.animateScrollToPage(pagerState.currentPage - 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
|
||||||
contentDescription = "Forward icon",
|
contentDescription = "Forward icon",
|
||||||
Modifier.fillMaxSize(),
|
Modifier.fillMaxSize(),
|
||||||
tint = MaterialTheme.colorScheme.primary)
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Box(Modifier.height(40.dp)
|
Box(
|
||||||
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
Modifier
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.height(40.dp)
|
||||||
|
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
||||||
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
) {
|
) {
|
||||||
Row(Modifier.fillMaxHeight().padding(10.dp, 0.dp),
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(10.dp, 0.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.DateRange,
|
Icon(
|
||||||
|
Icons.Default.DateRange,
|
||||||
contentDescription = "Date icon",
|
contentDescription = "Date icon",
|
||||||
Modifier.height(40.dp).padding(0.dp, 0.dp, 10.dp, 0.dp),
|
Modifier
|
||||||
tint = MaterialTheme.colorScheme.primary)
|
.height(40.dp)
|
||||||
Text(Utils.Date.format(
|
.padding(0.dp, 0.dp, 10.dp, 0.dp),
|
||||||
Utils.Date.addDays(currentDate.value,
|
tint = MaterialTheme.colorScheme.primary
|
||||||
pagerState.currentPage-currentDayOfWeek.intValue)
|
)
|
||||||
|
Text(
|
||||||
|
Utils.Date.format(
|
||||||
|
Utils.Date.addDays(
|
||||||
|
Date(),
|
||||||
|
pagerState.currentPage - Utils.Date.getDayOfWeek(
|
||||||
|
Date()
|
||||||
|
)+1
|
||||||
|
)
|
||||||
),
|
),
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
style = MaterialTheme.typography.bodyLarge)
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Box(Modifier.height(40.dp).width(40.dp)
|
Box(
|
||||||
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
Modifier
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
.height(40.dp)
|
||||||
.clickable {
|
.width(40.dp)
|
||||||
animationScope.launch {
|
.shadow(elevation = 6.dp, shape = RoundedCornerShape(50))
|
||||||
pagerState.animateScrollToPage(pagerState.currentPage+1)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
}
|
.clickable {
|
||||||
},
|
animationScope.launch {
|
||||||
|
pagerState.animateScrollToPage(pagerState.currentPage + 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||||
contentDescription = "Forward icon",
|
contentDescription = "Forward icon",
|
||||||
Modifier.fillMaxSize(),
|
Modifier.fillMaxSize(),
|
||||||
tint = MaterialTheme.colorScheme.primary)
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HorizontalDivider(Modifier.padding(20.dp, 0.dp))
|
HorizontalDivider(Modifier.padding(20.dp, 0.dp))
|
||||||
HorizontalPager(state = pagerState) { page ->
|
HorizontalPager(state = pagerState) { page ->
|
||||||
val todayLessons = lessons.value.filter { lesson ->
|
val todayLessons = lessons.value.filter { lesson ->
|
||||||
lesson.dayOfWeek-1 == Utils.Date.getDayOfWeek(
|
lesson.dayOfWeek - 1 == Utils.Date.getDayOfWeek(
|
||||||
Utils.Date.addDays(currentDate.value, page-currentDayOfWeek.intValue)) &&
|
Utils.Date.addDays(Date(), page - Utils.Date.getDayOfWeek(Date())+1)
|
||||||
|
) &&
|
||||||
lesson.week == Utils.Date.getWeekOfStudyYear(
|
lesson.week == Utils.Date.getWeekOfStudyYear(
|
||||||
Utils.Date.addDays(currentDate.value, page-currentDayOfWeek.intValue))
|
Utils.Date.addDays(Date(), page - Utils.Date.getDayOfWeek(Date())+1)
|
||||||
|
)
|
||||||
}.sortedBy { lesson -> lesson.beginTime }
|
}.sortedBy { lesson -> lesson.beginTime }
|
||||||
if(todayLessons.isEmpty())
|
if (todayLessons.isEmpty())
|
||||||
EmptyDay(Modifier)
|
EmptyDay(Modifier)
|
||||||
else
|
else
|
||||||
LessonCards(todayLessons)
|
LessonCards(todayLessons)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.example.ssau_schedule
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.util.Log
|
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -12,7 +11,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.LocalDate
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
@@ -45,6 +43,7 @@ class Utils {
|
|||||||
companion object {
|
companion object {
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
val StoreDateFormat = SimpleDateFormat("yyyy-MM-dd")
|
val StoreDateFormat = SimpleDateFormat("yyyy-MM-dd")
|
||||||
|
|
||||||
@SuppressLint("SimpleDateFormat")
|
@SuppressLint("SimpleDateFormat")
|
||||||
val DateFormat = SimpleDateFormat("dd MMMM")
|
val DateFormat = SimpleDateFormat("dd MMMM")
|
||||||
|
|
||||||
@@ -55,21 +54,23 @@ class Utils {
|
|||||||
fun getDayOfWeek(date: java.util.Date): Int {
|
fun getDayOfWeek(date: java.util.Date): Int {
|
||||||
val calendar = Calendar.getInstance()
|
val calendar = Calendar.getInstance()
|
||||||
calendar.time = date
|
calendar.time = date
|
||||||
return (calendar.get(Calendar.DAY_OF_WEEK)+5)%7
|
return (calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getWeekOfYear(date: java.util.Date): Int {
|
private fun getWeekOfYear(date: java.util.Date): Int {
|
||||||
val calendar = Calendar.getInstance()
|
val calendar = Calendar.getInstance()
|
||||||
calendar.time = date
|
calendar.time = date
|
||||||
return calendar.get(Calendar.WEEK_OF_YEAR) -
|
return calendar.get(Calendar.WEEK_OF_YEAR) -
|
||||||
(if(calendar.get(Calendar.DAY_OF_WEEK) == 1) 1 else 0)
|
(if (calendar.get(Calendar.DAY_OF_WEEK) == 0) 1 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWeekOfStudyYear(date: java.util.Date): Int {
|
fun getWeekOfStudyYear(date: java.util.Date): Int {
|
||||||
val calendar = Calendar.getInstance()
|
val calendar = Calendar.getInstance()
|
||||||
calendar.time = java.util.Date()
|
calendar.time = java.util.Date()
|
||||||
val year = calendar.get(Calendar.YEAR)
|
val year = calendar.get(Calendar.YEAR)
|
||||||
calendar.time = parse("${year}-09-01")
|
calendar.time = parse("${year}-09-01")
|
||||||
return getWeekOfYear(date) - (calendar.get(Calendar.WEEK_OF_YEAR) -
|
return getWeekOfYear(date) - (calendar.get(Calendar.WEEK_OF_YEAR) -
|
||||||
(if(calendar.get(Calendar.DAY_OF_WEEK) == 1) 1 else 0))
|
(if (calendar.get(Calendar.DAY_OF_WEEK) == 0) 1 else 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addDays(date: java.util.Date, days: Int): java.util.Date {
|
fun addDays(date: java.util.Date, days: Int): java.util.Date {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import okhttp3.MediaType.Companion.toMediaType
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
enum class AuthErrorMessage(private val resource: Int?) {
|
enum class AuthErrorMessage(private val resource: Int?) {
|
||||||
LOGIN_IS_TOO_SHORT(R.string.login_is_too_short),
|
LOGIN_IS_TOO_SHORT(R.string.login_is_too_short),
|
||||||
@@ -16,7 +15,7 @@ enum class AuthErrorMessage(private val resource: Int?) {
|
|||||||
INCORRECT_LOGIN_OR_PASSWORD(R.string.incorrect_login_or_password);
|
INCORRECT_LOGIN_OR_PASSWORD(R.string.incorrect_login_or_password);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthorizationAPI(private var http: Http) {
|
class AuthorizationAPI(private var http: Http) {
|
||||||
@@ -39,8 +38,9 @@ class AuthorizationAPI(private var http: Http) {
|
|||||||
).toString().toRequestBody("application/json".toMediaType()),
|
).toString().toRequestBody("application/json".toMediaType()),
|
||||||
mapOf(
|
mapOf(
|
||||||
Pair("Next-Action", "b395d17834d8b7df06372cbf1f241170a272d540")
|
Pair("Next-Action", "b395d17834d8b7df06372cbf1f241170a272d540")
|
||||||
).toHeaders())
|
).toHeaders()
|
||||||
val token = if(response?.headers?.toMap()?.containsKey("set-cookie") == true)
|
)
|
||||||
|
val token = if (response?.headers?.toMap()?.containsKey("set-cookie") == true)
|
||||||
response.headers("set-cookie").joinToString(", ") else null
|
response.headers("set-cookie").joinToString(", ") else null
|
||||||
return Pair(token, exception)
|
return Pair(token, exception)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ enum class GroupAPIErrorMessage(private val resource: Int?) {
|
|||||||
USER_NOT_AUTHORIZED(null);
|
USER_NOT_AUTHORIZED(null);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupAPI(private var http: Http) {
|
class GroupAPI(private var http: Http) {
|
||||||
@@ -25,9 +25,10 @@ class GroupAPI(private var http: Http) {
|
|||||||
BuildConfig.USER_GROUPS_URL,
|
BuildConfig.USER_GROUPS_URL,
|
||||||
mapOf(
|
mapOf(
|
||||||
Pair("Cookie", token)
|
Pair("Cookie", token)
|
||||||
).toHeaders())
|
).toHeaders()
|
||||||
if(response?.code == 401) return Pair(null, GroupAPIErrorMessage.USER_NOT_AUTHORIZED)
|
)
|
||||||
if(response?.body == null) return Pair(null, GroupAPIErrorMessage.FAILED_GET_USER_GROUPS)
|
if (response?.code == 401) return Pair(null, GroupAPIErrorMessage.USER_NOT_AUTHORIZED)
|
||||||
|
if (response?.body == null) return Pair(null, GroupAPIErrorMessage.FAILED_GET_USER_GROUPS)
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
val groups = Utils.Serializer
|
val groups = Utils.Serializer
|
||||||
|
|||||||
@@ -43,8 +43,12 @@ class Http {
|
|||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
if (!response.isSuccessful)
|
if (!response.isSuccessful)
|
||||||
coroutine.resume(Pair(response,
|
coroutine.resume(
|
||||||
HttpRequestException("Http response is not successful")))
|
Pair(
|
||||||
|
response,
|
||||||
|
HttpRequestException("Http response is not successful")
|
||||||
|
)
|
||||||
|
)
|
||||||
else coroutine.resume(Pair(response, null))
|
else coroutine.resume(Pair(response, null))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import android.util.Log
|
|||||||
import com.example.ssau_schedule.BuildConfig
|
import com.example.ssau_schedule.BuildConfig
|
||||||
import com.example.ssau_schedule.R
|
import com.example.ssau_schedule.R
|
||||||
import com.example.ssau_schedule.Utils
|
import com.example.ssau_schedule.Utils
|
||||||
import com.example.ssau_schedule.data.unsaved.APILessons
|
|
||||||
import com.example.ssau_schedule.data.store.Group
|
import com.example.ssau_schedule.data.store.Group
|
||||||
import com.example.ssau_schedule.data.store.Year
|
import com.example.ssau_schedule.data.store.Year
|
||||||
|
import com.example.ssau_schedule.data.unsaved.APILessons
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ enum class LessonAPIErrorMessage(private val resource: Int?) {
|
|||||||
USER_NOT_AUTHORIZED(null);
|
USER_NOT_AUTHORIZED(null);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
class LessonAPI(private var http: Http) {
|
class LessonAPI(private var http: Http) {
|
||||||
@@ -29,16 +29,20 @@ class LessonAPI(private var http: Http) {
|
|||||||
): Pair<APILessons?, LessonAPIErrorMessage?> {
|
): Pair<APILessons?, LessonAPIErrorMessage?> {
|
||||||
val (response) = http.request(
|
val (response) = http.request(
|
||||||
Method.GET,
|
Method.GET,
|
||||||
"${BuildConfig.LESSONS_URL}?yearId=${year.id}"+
|
"${BuildConfig.LESSONS_URL}?yearId=${year.id}" +
|
||||||
"&week=$week&userType=student&groupId=${group.id}",
|
"&week=$week&userType=student&groupId=${group.id}",
|
||||||
mapOf(
|
mapOf(
|
||||||
Pair("Cookie", token)
|
Pair("Cookie", token)
|
||||||
).toHeaders())
|
).toHeaders()
|
||||||
if(response?.code == 401) return Pair(null, LessonAPIErrorMessage.USER_NOT_AUTHORIZED)
|
)
|
||||||
if(response?.body == null) return Pair(null, LessonAPIErrorMessage.FAILED_GET_LESSONS)
|
if (response?.code == 401) return Pair(null, LessonAPIErrorMessage.USER_NOT_AUTHORIZED)
|
||||||
try { return Pair(Utils.Serializer
|
if (response?.body == null) return Pair(null, LessonAPIErrorMessage.FAILED_GET_LESSONS)
|
||||||
.decodeFromString<APILessons>(response.body!!.string()), null)
|
try {
|
||||||
} catch(e: SerializationException) {
|
return Pair(
|
||||||
|
Utils.Serializer
|
||||||
|
.decodeFromString<APILessons>(response.body!!.string()), null
|
||||||
|
)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
Log.e("Serialization error", e.message.toString())
|
Log.e("Serialization error", e.message.toString())
|
||||||
return Pair(null, LessonAPIErrorMessage.FAILED_GET_LESSONS)
|
return Pair(null, LessonAPIErrorMessage.FAILED_GET_LESSONS)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ enum class UserAPIErrorMessage(private val resource: Int?) {
|
|||||||
USER_NOT_AUTHORIZED(null);
|
USER_NOT_AUTHORIZED(null);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserAPI(private var http: Http) {
|
class UserAPI(private var http: Http) {
|
||||||
@@ -26,12 +26,13 @@ class UserAPI(private var http: Http) {
|
|||||||
BuildConfig.USER_DETAILS_URL,
|
BuildConfig.USER_DETAILS_URL,
|
||||||
mapOf(
|
mapOf(
|
||||||
Pair("Cookie", token)
|
Pair("Cookie", token)
|
||||||
).toHeaders())
|
).toHeaders()
|
||||||
if(response?.code == 401) return Pair(null, UserAPIErrorMessage.USER_NOT_AUTHORIZED)
|
)
|
||||||
if(response?.body == null) return Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
if (response?.code == 401) return Pair(null, UserAPIErrorMessage.USER_NOT_AUTHORIZED)
|
||||||
|
if (response?.body == null) return Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
||||||
return try {
|
return try {
|
||||||
Pair(Utils.Serializer.decodeFromString<User>(response.body!!.string()), null)
|
Pair(Utils.Serializer.decodeFromString<User>(response.body!!.string()), null)
|
||||||
} catch(e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
Pair(null, UserAPIErrorMessage.FAILED_GET_USER_DETAILS)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ enum class YearAPIErrorMessage(private val resource: Int?) {
|
|||||||
USER_NOT_AUTHORIZED(null);
|
USER_NOT_AUTHORIZED(null);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
class YearAPI(private var http: Http) {
|
class YearAPI(private var http: Http) {
|
||||||
@@ -26,19 +26,18 @@ class YearAPI(private var http: Http) {
|
|||||||
BuildConfig.YEARS_URL,
|
BuildConfig.YEARS_URL,
|
||||||
mapOf(
|
mapOf(
|
||||||
Pair("Cookie", token)
|
Pair("Cookie", token)
|
||||||
).toHeaders())
|
).toHeaders()
|
||||||
if(response?.code == 401) return Pair(null, YearAPIErrorMessage.USER_NOT_AUTHORIZED)
|
)
|
||||||
if(response?.body == null) return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
if (response?.code == 401) return Pair(null, YearAPIErrorMessage.USER_NOT_AUTHORIZED)
|
||||||
|
if (response?.body == null) return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
||||||
try {
|
try {
|
||||||
val rawYears = Utils.Serializer
|
val rawYears = Utils.Serializer
|
||||||
.decodeFromString<List<RawYear>>(response.body!!.string())
|
.decodeFromString<List<RawYear>>(response.body!!.string())
|
||||||
return if(rawYears.isNotEmpty()) Pair(rawYears, null)
|
return if (rawYears.isNotEmpty()) Pair(rawYears, null)
|
||||||
else Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
else Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
||||||
}
|
} catch (e: SerializationException) {
|
||||||
catch(e: SerializationException) {
|
|
||||||
return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
||||||
}
|
} catch (e: IllegalArgumentException) {
|
||||||
catch (e: IllegalArgumentException) {
|
|
||||||
return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
return Pair(null, YearAPIErrorMessage.FAILED_GET_YEARS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,50 @@
|
|||||||
package com.example.ssau_schedule.components
|
package com.example.ssau_schedule.components
|
||||||
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
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.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.ssau_schedule.R
|
||||||
import com.example.ssau_schedule.ui.theme.LessonColors
|
import com.example.ssau_schedule.ui.theme.LessonColors
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmptyDay(modifier: Modifier) {
|
fun EmptyDay(modifier: Modifier) {
|
||||||
Box(Modifier.fillMaxHeight().fillMaxWidth()) {
|
Box(
|
||||||
Row(modifier.fillMaxWidth()
|
Modifier
|
||||||
.padding(14.dp, 8.dp)
|
.fillMaxHeight()
|
||||||
.shadow(elevation = 6.dp, shape = RoundedCornerShape(12.dp))
|
.fillMaxWidth()) {
|
||||||
.background(
|
Row(
|
||||||
if(isSystemInDarkTheme()) LessonColors.Background.Dark.Unknown
|
modifier
|
||||||
else LessonColors.Background.Light.Unknown
|
.fillMaxWidth()
|
||||||
),
|
.padding(14.dp, 8.dp)
|
||||||
|
.shadow(elevation = 6.dp, shape = RoundedCornerShape(12.dp))
|
||||||
|
.background(
|
||||||
|
if (isSystemInDarkTheme()) LessonColors.Background.Dark.Unknown
|
||||||
|
else LessonColors.Background.Light.Unknown
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
AutoResizeText("Сегодня нет занятий",
|
AutoResizeText(
|
||||||
modifier = modifier.fillMaxWidth().padding(14.dp),
|
stringResource(R.string.no_classes_today),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(14.dp),
|
||||||
fontSizeRange = FontSizeRange(10.sp, 24.sp),
|
fontSizeRange = FontSizeRange(10.sp, 24.sp),
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
textAlign = TextAlign.Center)
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,44 +25,69 @@ import com.example.ssau_schedule.ui.theme.LessonColors
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LessonCard(modifier: Modifier, lesson: Lesson) {
|
fun LessonCard(modifier: Modifier, lesson: Lesson) {
|
||||||
Row(modifier.fillMaxWidth()
|
Row(
|
||||||
.height(130.dp).padding(14.dp, 8.dp)
|
modifier
|
||||||
.shadow(elevation = 6.dp, shape = RoundedCornerShape(12.dp))
|
.fillMaxWidth()
|
||||||
.background(
|
.height(130.dp)
|
||||||
if(isSystemInDarkTheme())
|
.padding(14.dp, 8.dp)
|
||||||
lesson.type?.darkBackground ?: LessonColors.Background.Dark.Unknown
|
.shadow(elevation = 6.dp, shape = RoundedCornerShape(12.dp))
|
||||||
else lesson.type?.lightBackground ?: LessonColors.Background.Light.Unknown
|
.background(
|
||||||
)
|
if (isSystemInDarkTheme())
|
||||||
|
lesson.type?.darkBackground ?: LessonColors.Background.Dark.Unknown
|
||||||
|
else lesson.type?.lightBackground ?: LessonColors.Background.Light.Unknown
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Box(modifier.fillMaxHeight().width(16.dp).shadow(4.dp)
|
Box(
|
||||||
.background(lesson.type?.foreground ?: LessonColors.Foreground.Unknown))
|
modifier
|
||||||
Column(modifier.fillMaxHeight().padding(10.dp, 10.dp),
|
.fillMaxHeight()
|
||||||
verticalArrangement = Arrangement.SpaceBetween) {
|
.width(16.dp)
|
||||||
Row(modifier.fillMaxWidth(),
|
.shadow(4.dp)
|
||||||
horizontalArrangement = Arrangement.SpaceBetween) {
|
.background(lesson.type?.foreground ?: LessonColors.Foreground.Unknown)
|
||||||
Text("${lesson.beginTime} - ${lesson.endTime}",
|
)
|
||||||
|
Column(
|
||||||
|
modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.padding(10.dp, 10.dp),
|
||||||
|
verticalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"${lesson.beginTime} - ${lesson.endTime}",
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
style = MaterialTheme.typography.bodyMedium)
|
style = MaterialTheme.typography.bodyMedium
|
||||||
Text("512 - 5",
|
)
|
||||||
|
Text(
|
||||||
|
"${lesson.room ?: "???"} - ${lesson.building ?: "?"}",
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
style = MaterialTheme.typography.bodyMedium)
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
}
|
}
|
||||||
AutoResizeText(lesson.discipline,
|
AutoResizeText(
|
||||||
|
lesson.discipline,
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
fontSizeRange = FontSizeRange(10.sp, 24.sp),
|
fontSizeRange = FontSizeRange(10.sp, 24.sp),
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.titleLarge)
|
style = MaterialTheme.typography.titleLarge
|
||||||
Text(lesson.teacher,
|
)
|
||||||
|
Text(
|
||||||
|
lesson.teacher,
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
style = MaterialTheme.typography.titleSmall)
|
style = MaterialTheme.typography.titleSmall
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LessonCards(lessons: List<Lesson>) {
|
fun LessonCards(lessons: List<Lesson>) {
|
||||||
Box(Modifier.fillMaxHeight().fillMaxWidth()) {
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.fillMaxWidth()) {
|
||||||
LazyColumn {
|
LazyColumn {
|
||||||
items(lessons.count()) {
|
items(lessons.count()) {
|
||||||
LessonCard(Modifier, lessons[it])
|
LessonCard(Modifier, lessons[it])
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.example.ssau_schedule.data.base
|
package com.example.ssau_schedule.data.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.room.AutoMigration
|
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
@@ -11,14 +10,17 @@ import com.example.ssau_schedule.data.base.entity.lesson.Lesson
|
|||||||
import com.example.ssau_schedule.data.base.entity.lesson.LessonType
|
import com.example.ssau_schedule.data.base.entity.lesson.LessonType
|
||||||
|
|
||||||
class Converters {
|
class Converters {
|
||||||
@TypeConverter fun toLessonType(value: String) = LessonType.getTypeFromName(value)
|
@TypeConverter
|
||||||
@TypeConverter fun fromLessonType(value: LessonType) = value.displayName
|
fun toLessonType(value: String) = LessonType.getTypeFromName(value)
|
||||||
|
@TypeConverter
|
||||||
|
fun fromLessonType(value: LessonType) = value.displayName
|
||||||
}
|
}
|
||||||
|
|
||||||
@androidx.room.Database(
|
@androidx.room.Database(
|
||||||
entities = [Lesson::class],
|
entities = [Lesson::class],
|
||||||
version = 1,
|
version = 1,
|
||||||
autoMigrations = [])
|
autoMigrations = []
|
||||||
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class Database : RoomDatabase() {
|
abstract class Database : RoomDatabase() {
|
||||||
abstract fun lessonDao(): LessonDao
|
abstract fun lessonDao(): LessonDao
|
||||||
|
|||||||
@@ -13,34 +13,48 @@ enum class LessonType(
|
|||||||
val darkBackground: Color,
|
val darkBackground: Color,
|
||||||
val lightBackground: Color,
|
val lightBackground: Color,
|
||||||
) {
|
) {
|
||||||
LECTURE("Лекция",
|
LECTURE(
|
||||||
|
"Лекция",
|
||||||
LessonColors.Foreground.Lecture,
|
LessonColors.Foreground.Lecture,
|
||||||
LessonColors.Background.Dark.Lecture,
|
LessonColors.Background.Dark.Lecture,
|
||||||
LessonColors.Background.Light.Lecture),
|
LessonColors.Background.Light.Lecture
|
||||||
PRACTICE("Практика",
|
),
|
||||||
|
PRACTICE(
|
||||||
|
"Практика",
|
||||||
LessonColors.Foreground.Practice,
|
LessonColors.Foreground.Practice,
|
||||||
LessonColors.Background.Dark.Practice,
|
LessonColors.Background.Dark.Practice,
|
||||||
LessonColors.Background.Light.Practice),
|
LessonColors.Background.Light.Practice
|
||||||
LABORATORY("Лабораторная",
|
),
|
||||||
|
LABORATORY(
|
||||||
|
"Лабораторная",
|
||||||
LessonColors.Foreground.Laboratory,
|
LessonColors.Foreground.Laboratory,
|
||||||
LessonColors.Background.Dark.Laboratory,
|
LessonColors.Background.Dark.Laboratory,
|
||||||
LessonColors.Background.Light.Laboratory),
|
LessonColors.Background.Light.Laboratory
|
||||||
OTHER("Другое",
|
),
|
||||||
|
OTHER(
|
||||||
|
"Другое",
|
||||||
LessonColors.Foreground.Other,
|
LessonColors.Foreground.Other,
|
||||||
LessonColors.Background.Dark.Other,
|
LessonColors.Background.Dark.Other,
|
||||||
LessonColors.Background.Light.Other),
|
LessonColors.Background.Light.Other
|
||||||
EXAMINATION("Экзамен",
|
),
|
||||||
|
EXAMINATION(
|
||||||
|
"Экзамен",
|
||||||
LessonColors.Foreground.Examination,
|
LessonColors.Foreground.Examination,
|
||||||
LessonColors.Background.Dark.Examination,
|
LessonColors.Background.Dark.Examination,
|
||||||
LessonColors.Background.Light.Examination),
|
LessonColors.Background.Light.Examination
|
||||||
TEST("Зачёт",
|
),
|
||||||
|
TEST(
|
||||||
|
"Зачёт",
|
||||||
LessonColors.Foreground.Test,
|
LessonColors.Foreground.Test,
|
||||||
LessonColors.Background.Dark.Test,
|
LessonColors.Background.Dark.Test,
|
||||||
LessonColors.Background.Light.Test),
|
LessonColors.Background.Light.Test
|
||||||
CONSULTATION("Консультация",
|
),
|
||||||
|
CONSULTATION(
|
||||||
|
"Консультация",
|
||||||
LessonColors.Foreground.Consultation,
|
LessonColors.Foreground.Consultation,
|
||||||
LessonColors.Background.Dark.Consultation,
|
LessonColors.Background.Dark.Consultation,
|
||||||
LessonColors.Background.Light.Consultation);
|
LessonColors.Background.Light.Consultation
|
||||||
|
);
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getTypeFromName(name: String) =
|
fun getTypeFromName(name: String) =
|
||||||
@@ -60,4 +74,6 @@ data class Lesson(
|
|||||||
@ColumnInfo(name = "begin_time") val beginTime: String,
|
@ColumnInfo(name = "begin_time") val beginTime: String,
|
||||||
@ColumnInfo(name = "end_time") val endTime: String,
|
@ColumnInfo(name = "end_time") val endTime: String,
|
||||||
@ColumnInfo(name = "conference_url") val conferenceUrl: String?,
|
@ColumnInfo(name = "conference_url") val conferenceUrl: String?,
|
||||||
|
@ColumnInfo(name = "building") val building: String?,
|
||||||
|
@ColumnInfo(name = "room") val room: String?,
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,9 @@ class AuthStore {
|
|||||||
suspend fun setAuthToken(
|
suspend fun setAuthToken(
|
||||||
token: String,
|
token: String,
|
||||||
context: Context,
|
context: Context,
|
||||||
) { context.authStore.edit { authStore -> authStore[Keys.AUTH_TOKEN] = token } }
|
) {
|
||||||
|
context.authStore.edit { authStore -> authStore[Keys.AUTH_TOKEN] = token }
|
||||||
|
}
|
||||||
|
|
||||||
fun setAuthToken(
|
fun setAuthToken(
|
||||||
token: String,
|
token: String,
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ class GroupStore {
|
|||||||
.map { groupStore -> groupStore[Keys.CURRENT_GROUP_ID] }.first()
|
.map { groupStore -> groupStore[Keys.CURRENT_GROUP_ID] }.first()
|
||||||
val currentGroupName = context.groupStore.data
|
val currentGroupName = context.groupStore.data
|
||||||
.map { groupStore -> groupStore[Keys.CURRENT_GROUP_NAME] }.first()
|
.map { groupStore -> groupStore[Keys.CURRENT_GROUP_NAME] }.first()
|
||||||
return if(currentGroupId != null && currentGroupName != null)
|
return if (currentGroupId != null && currentGroupName != null)
|
||||||
Group(id = currentGroupId,
|
Group(
|
||||||
name = currentGroupName)
|
id = currentGroupId,
|
||||||
|
name = currentGroupName
|
||||||
|
)
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package com.example.ssau_schedule.data.store
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.coroutines.resume
|
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
data class GeneralData(
|
data class GeneralData(
|
||||||
val token: String,
|
val token: String,
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ data class Year(
|
|||||||
calendar.time = startDate
|
calendar.time = startDate
|
||||||
val firstWeek = calendar.get(Calendar.WEEK_OF_YEAR)
|
val firstWeek = calendar.get(Calendar.WEEK_OF_YEAR)
|
||||||
calendar.time = date
|
calendar.time = date
|
||||||
return (calendar.get(Calendar.WEEK_OF_YEAR) - firstWeek)+1
|
return (calendar.get(Calendar.WEEK_OF_YEAR) - firstWeek) + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ class YearStore {
|
|||||||
val CURRENT_YEAR_END = stringPreferencesKey("year_end")
|
val CURRENT_YEAR_END = stringPreferencesKey("year_end")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun setCurrentYear(
|
suspend fun setCurrentYear(
|
||||||
year: Year,
|
year: Year,
|
||||||
@@ -80,17 +81,22 @@ class YearStore {
|
|||||||
val currentYearId = context.yearStore.data
|
val currentYearId = context.yearStore.data
|
||||||
.map { yearStore -> yearStore[Keys.CURRENT_YEAR_ID] }.first()
|
.map { yearStore -> yearStore[Keys.CURRENT_YEAR_ID] }.first()
|
||||||
val currentYearStartDate = context.yearStore.data
|
val currentYearStartDate = context.yearStore.data
|
||||||
.map { yearStore -> yearStore[Keys.CURRENT_YEAR_START]
|
.map { yearStore ->
|
||||||
|
yearStore[Keys.CURRENT_YEAR_START]
|
||||||
}.first()
|
}.first()
|
||||||
val currentYearEndDate = context.yearStore.data
|
val currentYearEndDate = context.yearStore.data
|
||||||
.map { yearStore -> yearStore[Keys.CURRENT_YEAR_END]
|
.map { yearStore ->
|
||||||
|
yearStore[Keys.CURRENT_YEAR_END]
|
||||||
}.first()
|
}.first()
|
||||||
return if(currentYearId != null &&
|
return if (currentYearId != null &&
|
||||||
currentYearStartDate != null &&
|
currentYearStartDate != null &&
|
||||||
currentYearEndDate != null)
|
currentYearEndDate != null
|
||||||
Year(id = currentYearId,
|
)
|
||||||
|
Year(
|
||||||
|
id = currentYearId,
|
||||||
startDate = Utils.Date.parse(currentYearStartDate),
|
startDate = Utils.Date.parse(currentYearStartDate),
|
||||||
endDate = Utils.Date.parse(currentYearEndDate))
|
endDate = Utils.Date.parse(currentYearEndDate)
|
||||||
|
)
|
||||||
else null
|
else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,16 +11,34 @@ enum class LessonConverterErrorMessage(private val resource: Int?) {
|
|||||||
NO_DISCIPLINE_FOR_IET_LESSON(R.string.failed_get_lessons);
|
NO_DISCIPLINE_FOR_IET_LESSON(R.string.failed_get_lessons);
|
||||||
|
|
||||||
fun getMessage(context: Context) =
|
fun getMessage(context: Context) =
|
||||||
if(resource != null) context.getString(resource) else null
|
if (resource != null) context.getString(resource) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable data class APILessonType(val name: String)
|
@Serializable
|
||||||
@Serializable data class APILessonDiscipline(val name: String)
|
data class APILessonType(val name: String)
|
||||||
@Serializable data class APILessonTeacher(val name: String)
|
@Serializable
|
||||||
@Serializable data class APILessonTime(val beginTime: String, val endTime: String)
|
data class APILessonDiscipline(val name: String)
|
||||||
@Serializable data class APILessonConference(val url: String)
|
@Serializable
|
||||||
@Serializable data class APILessonFlow(val discipline: APILessonDiscipline)
|
data class APILessonTeacher(val name: String)
|
||||||
@Serializable data class APILessonWeekDay(val id: Int)
|
@Serializable
|
||||||
|
data class APILessonTime(val beginTime: String, val endTime: String)
|
||||||
|
@Serializable
|
||||||
|
data class APILessonConference(val url: String)
|
||||||
|
@Serializable
|
||||||
|
data class APILessonFlow(val discipline: APILessonDiscipline)
|
||||||
|
@Serializable
|
||||||
|
data class APILessonWeekDay(val id: Int)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class APILessonBuilding(val name: String)
|
||||||
|
@Serializable
|
||||||
|
data class APILessonRoom(val name: String)
|
||||||
|
@Serializable
|
||||||
|
data class APILessonWeeks(
|
||||||
|
val building: APILessonBuilding,
|
||||||
|
val room: APILessonRoom,
|
||||||
|
val week: Int
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class APILesson(
|
data class APILesson(
|
||||||
@@ -30,21 +48,27 @@ data class APILesson(
|
|||||||
val teachers: List<APILessonTeacher>,
|
val teachers: List<APILessonTeacher>,
|
||||||
val time: APILessonTime,
|
val time: APILessonTime,
|
||||||
val conference: APILessonConference?,
|
val conference: APILessonConference?,
|
||||||
val weekday: APILessonWeekDay
|
val weekday: APILessonWeekDay,
|
||||||
|
val weeks: List<APILessonWeeks>
|
||||||
) {
|
) {
|
||||||
fun toLesson(week: Int): Pair<Lesson?, LessonConverterErrorMessage?> {
|
fun toLesson(week: Int): Pair<Lesson?, LessonConverterErrorMessage?> {
|
||||||
return if(teachers.isEmpty()) Pair(null, LessonConverterErrorMessage.NO_TEACHER_FOR_LESSON)
|
val weekInfo = weeks.find { w -> w.week == week }
|
||||||
else Pair(Lesson(
|
return if (teachers.isEmpty()) Pair(null, LessonConverterErrorMessage.NO_TEACHER_FOR_LESSON)
|
||||||
id = id,
|
else Pair(
|
||||||
type = LessonType.getTypeFromName(type.name),
|
Lesson(
|
||||||
discipline = discipline.name,
|
id = id,
|
||||||
teacher = teachers[0].name,
|
type = LessonType.getTypeFromName(type.name),
|
||||||
beginTime = time.beginTime,
|
discipline = discipline.name,
|
||||||
endTime = time.endTime,
|
teacher = teachers[0].name,
|
||||||
conferenceUrl = conference?.url,
|
beginTime = time.beginTime,
|
||||||
dayOfWeek = weekday.id,
|
endTime = time.endTime,
|
||||||
week = week
|
conferenceUrl = conference?.url,
|
||||||
), null)
|
dayOfWeek = weekday.id,
|
||||||
|
week = week,
|
||||||
|
building = weekInfo?.building?.name,
|
||||||
|
room = weekInfo?.room?.name
|
||||||
|
), null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -57,22 +81,31 @@ data class APIIETLesson(
|
|||||||
val teachers: List<APILessonTeacher>,
|
val teachers: List<APILessonTeacher>,
|
||||||
val time: APILessonTime,
|
val time: APILessonTime,
|
||||||
val conference: APILessonConference?,
|
val conference: APILessonConference?,
|
||||||
val weekday: APILessonWeekDay
|
val weekday: APILessonWeekDay,
|
||||||
|
val weeks: List<APILessonWeeks>
|
||||||
) {
|
) {
|
||||||
fun toLesson(week: Int): Pair<Lesson?, LessonConverterErrorMessage?> {
|
fun toLesson(week: Int): Pair<Lesson?, LessonConverterErrorMessage?> {
|
||||||
return if(teachers.isEmpty()) Pair(null, LessonConverterErrorMessage.NO_TEACHER_FOR_LESSON)
|
val weekInfo = weeks.find { w -> w.week == week }
|
||||||
else if(flows.isEmpty()) Pair(null, LessonConverterErrorMessage.NO_DISCIPLINE_FOR_IET_LESSON)
|
return if (teachers.isEmpty()) Pair(null, LessonConverterErrorMessage.NO_TEACHER_FOR_LESSON)
|
||||||
else Pair(Lesson(
|
else if (flows.isEmpty()) Pair(
|
||||||
id = id,
|
null,
|
||||||
type = LessonType.getTypeFromName(type.name),
|
LessonConverterErrorMessage.NO_DISCIPLINE_FOR_IET_LESSON
|
||||||
discipline = flows[0].discipline.name,
|
)
|
||||||
teacher = teachers[0].name,
|
else Pair(
|
||||||
beginTime = time.beginTime,
|
Lesson(
|
||||||
endTime = time.endTime,
|
id = id,
|
||||||
conferenceUrl = conference?.url,
|
type = LessonType.getTypeFromName(type.name),
|
||||||
dayOfWeek = weekday.id,
|
discipline = flows[0].discipline.name,
|
||||||
week = week
|
teacher = teachers[0].name,
|
||||||
), null)
|
beginTime = time.beginTime,
|
||||||
|
endTime = time.endTime,
|
||||||
|
conferenceUrl = conference?.url,
|
||||||
|
dayOfWeek = weekday.id,
|
||||||
|
week = week,
|
||||||
|
building = weekInfo?.building?.name,
|
||||||
|
room = weekInfo?.room?.name
|
||||||
|
), null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,13 +119,13 @@ data class APILessons(
|
|||||||
val exceptions = mutableListOf<LessonConverterErrorMessage>()
|
val exceptions = mutableListOf<LessonConverterErrorMessage>()
|
||||||
lessons.forEach { lesson ->
|
lessons.forEach { lesson ->
|
||||||
val (databaseLesson, exception) = lesson.toLesson(week)
|
val (databaseLesson, exception) = lesson.toLesson(week)
|
||||||
if(databaseLesson != null) databaseLessons.add(databaseLesson)
|
if (databaseLesson != null) databaseLessons.add(databaseLesson)
|
||||||
if(exception != null) exceptions.add(exception)
|
if (exception != null) exceptions.add(exception)
|
||||||
}
|
}
|
||||||
ietLessons.forEach { ietLesson ->
|
ietLessons.forEach { ietLesson ->
|
||||||
val (databaseIetLesson, exception) = ietLesson.toLesson(week)
|
val (databaseIetLesson, exception) = ietLesson.toLesson(week)
|
||||||
if(databaseIetLesson != null) databaseLessons.add(databaseIetLesson)
|
if (databaseIetLesson != null) databaseLessons.add(databaseIetLesson)
|
||||||
if(exception != null) exceptions.add(exception)
|
if (exception != null) exceptions.add(exception)
|
||||||
}
|
}
|
||||||
return Pair(databaseLessons, exceptions)
|
return Pair(databaseLessons, exceptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class LessonColors {
|
|||||||
val Unknown = Color(0xFFE2E2E2)
|
val Unknown = Color(0xFFE2E2E2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Dark {
|
class Dark {
|
||||||
companion object {
|
companion object {
|
||||||
val Lecture = Color(0xFF444946)
|
val Lecture = Color(0xFF444946)
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.color.ColorProviders
|
||||||
|
import androidx.glance.material3.ColorProviders
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = ApplicationColors.Primary01,
|
primary = ApplicationColors.Primary01,
|
||||||
@@ -31,7 +34,16 @@ fun SSAU_ScheduleTheme(
|
|||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) = MaterialTheme(
|
) = MaterialTheme(
|
||||||
colorScheme = if(darkTheme) DarkColorScheme else LightColorScheme,
|
colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SSAU_ScheduleWidgetTheme(
|
||||||
|
colors: ColorProviders = ColorProviders(light = LightColorScheme, dark = DarkColorScheme),
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) = GlanceTheme(
|
||||||
|
colors = colors,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.example.ssau_schedule.widget
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.layout.Box
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxHeight
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.height
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.layout.width
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextAlign
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import com.example.ssau_schedule.data.base.entity.lesson.Lesson
|
||||||
|
import com.example.ssau_schedule.ui.theme.LessonColors
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LessonCard(lesson: Lesson) {
|
||||||
|
Row(
|
||||||
|
GlanceModifier.fillMaxWidth()
|
||||||
|
.cornerRadius(12.dp)
|
||||||
|
.background(lesson.type?.darkBackground ?: LessonColors.Background.Dark.Unknown)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
GlanceModifier.fillMaxHeight().width(10.dp)
|
||||||
|
.background(lesson.type?.foreground ?: LessonColors.Foreground.Unknown)
|
||||||
|
) { }
|
||||||
|
Column(GlanceModifier.fillMaxHeight().padding(10.dp, 10.dp)) {
|
||||||
|
Text(
|
||||||
|
"${lesson.beginTime} - ${lesson.endTime}, ${lesson.room ?: "???"} - ${lesson.building ?: "?"}",
|
||||||
|
style = TextStyle(
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
fontSize = 14.sp, color = GlanceTheme.colors.tertiary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(GlanceModifier.fillMaxWidth().height(4.dp))
|
||||||
|
Text(
|
||||||
|
lesson.discipline,
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
style = TextStyle(fontSize = 20.sp, color = GlanceTheme.colors.secondary)
|
||||||
|
)
|
||||||
|
Spacer(GlanceModifier.fillMaxWidth().height(4.dp))
|
||||||
|
Text(
|
||||||
|
lesson.teacher,
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
style = TextStyle(fontSize = 12.sp, color = GlanceTheme.colors.tertiary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LessonCards(lessons: List<Lesson>) {
|
||||||
|
lessons.forEach { lesson ->
|
||||||
|
LessonCard(lesson)
|
||||||
|
}
|
||||||
|
}
|
||||||
104
app/src/main/java/com/example/ssau_schedule/widget/Widget.kt
Normal file
104
app/src/main/java/com/example/ssau_schedule/widget/Widget.kt
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package com.example.ssau_schedule.widget
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidgetReceiver
|
||||||
|
import androidx.glance.appwidget.cornerRadius
|
||||||
|
import androidx.glance.appwidget.lazy.LazyColumn
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.layout.Box
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.height
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextAlign
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import com.example.ssau_schedule.R
|
||||||
|
import com.example.ssau_schedule.Utils
|
||||||
|
import com.example.ssau_schedule.data.base.Database
|
||||||
|
import com.example.ssau_schedule.data.base.entity.lesson.Lesson
|
||||||
|
import com.example.ssau_schedule.ui.theme.LessonColors
|
||||||
|
import com.example.ssau_schedule.ui.theme.SSAU_ScheduleWidgetTheme
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class ScheduleWidget : GlanceAppWidget() {
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
provideContent {
|
||||||
|
SSAU_ScheduleWidgetTheme {
|
||||||
|
WidgetContent(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SimpleDateFormat")
|
||||||
|
@Composable
|
||||||
|
private fun WidgetContent(context: Context) {
|
||||||
|
val database = remember { Database.getInstance(context) }
|
||||||
|
val lessons = remember { mutableStateOf<List<Lesson>>(listOf()) }
|
||||||
|
LaunchedEffect(false) {
|
||||||
|
lessons.value = database.lessonDao().getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(GlanceModifier.fillMaxSize().background(GlanceTheme.colors.surface)) {
|
||||||
|
LazyColumn {
|
||||||
|
items(7) {
|
||||||
|
Column {
|
||||||
|
val todayLessons = lessons.value.filter { lesson ->
|
||||||
|
lesson.dayOfWeek - 1 == Utils.Date.getDayOfWeek(
|
||||||
|
Utils.Date.addDays(
|
||||||
|
Date(),
|
||||||
|
it
|
||||||
|
)
|
||||||
|
) &&
|
||||||
|
lesson.week == Utils.Date.getWeekOfStudyYear(Date())
|
||||||
|
}.sortedBy { lesson -> lesson.beginTime }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
GlanceModifier.fillMaxWidth()
|
||||||
|
.background(LessonColors.Background.Dark.Unknown)
|
||||||
|
.padding(20.dp, 10.dp).cornerRadius(12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
SimpleDateFormat("d MMMM").format(Utils.Date.addDays(
|
||||||
|
Date(),
|
||||||
|
it
|
||||||
|
)) + if(todayLessons.isEmpty()) " - "+context.getString(R.string.no_classes) else "",
|
||||||
|
modifier = GlanceModifier.fillMaxWidth(),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 14.sp,
|
||||||
|
textAlign = TextAlign.Left,
|
||||||
|
color = GlanceTheme.colors.tertiary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(todayLessons.isNotEmpty()) LessonCards(todayLessons)
|
||||||
|
Spacer(GlanceModifier.fillMaxWidth().height(10.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WidgetReceiver : GlanceAppWidgetReceiver() {
|
||||||
|
override val glanceAppWidget: GlanceAppWidget = ScheduleWidget()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,50 +1,83 @@
|
|||||||
package com.example.ssau_schedule.work
|
package com.example.ssau_schedule.work
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.glance.appwidget.updateAll
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.ForegroundInfo
|
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
|
import com.example.ssau_schedule.R
|
||||||
import com.example.ssau_schedule.api.Http
|
import com.example.ssau_schedule.api.Http
|
||||||
import com.example.ssau_schedule.api.LessonAPI
|
import com.example.ssau_schedule.api.LessonAPI
|
||||||
import com.example.ssau_schedule.data.base.Database
|
import com.example.ssau_schedule.data.base.Database
|
||||||
import com.example.ssau_schedule.data.store.StoreUtils
|
import com.example.ssau_schedule.data.store.StoreUtils
|
||||||
|
import com.example.ssau_schedule.widget.ScheduleWidget
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
//class RequestLessonsWorker(
|
|
||||||
// private val context: Context,
|
class RequestLessonsWorker(
|
||||||
// private val workerParams: WorkerParameters
|
private val context: Context,
|
||||||
//): CoroutineWorker(context, workerParams) {
|
workerParams: WorkerParameters
|
||||||
// private val notificationManager =
|
) : CoroutineWorker(context, workerParams) {
|
||||||
// context.getSystemService(Context.NOTIFICATION_SERVICE) as
|
private val notificationManager =
|
||||||
// NotificationManager
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as
|
||||||
//
|
NotificationManager
|
||||||
// override suspend fun doWork(): Result {
|
private val channelId = "ssau_schedule_1"
|
||||||
// val http = Http()
|
private val notificationId = 1234
|
||||||
// val lessonAPI = LessonAPI(http)
|
|
||||||
// val database = Database.getInstance(context)
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
//
|
override suspend fun doWork(): Result {
|
||||||
// val generalData = StoreUtils.getGeneralData(context) ?: return Result.failure()
|
val mChannel = NotificationChannel(
|
||||||
// val week = inputData.getInt("week", -1)
|
channelId,
|
||||||
// if(week == -1) return Result.failure()
|
"SSAUScheduleNotificationChannel", NotificationManager.IMPORTANCE_HIGH
|
||||||
//
|
)
|
||||||
//
|
mChannel.enableLights(true)
|
||||||
// val (apiLessons, apiErrors) = lessonAPI.getLessons(
|
mChannel.enableVibration(true)
|
||||||
// generalData.token,
|
notificationManager.createNotificationChannel(mChannel)
|
||||||
// generalData.group,
|
|
||||||
// generalData.year,
|
val http = Http()
|
||||||
// week,
|
val lessonAPI = LessonAPI(http)
|
||||||
// )
|
val database = Database.getInstance(context)
|
||||||
// if(apiErrors != null || apiLessons == null) return Result.failure()
|
|
||||||
//
|
val generalData = StoreUtils.getGeneralData(context)
|
||||||
// val (lessons, convertErrors) = apiLessons.toLessons(week)
|
if (generalData == null) {
|
||||||
// if(convertErrors.isNotEmpty()) {
|
pushErrorNotification()
|
||||||
// var builder = NotificationCompat.Builder(context, "1")
|
return Result.failure()
|
||||||
// .setContentTitle("Title")
|
}
|
||||||
// .setContentText("Content")
|
val week = generalData.year.getWeekOfDate(Date())
|
||||||
// .setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
val (apiLessons, apiErrors) = lessonAPI.getLessons(
|
||||||
// }
|
generalData.token,
|
||||||
// database.lessonDao().insert(*lessons.to)
|
generalData.group,
|
||||||
// }
|
generalData.year,
|
||||||
//}
|
week,
|
||||||
|
)
|
||||||
|
if (apiErrors != null || apiLessons == null) {
|
||||||
|
pushErrorNotification()
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
val (lessons, convertErrors) = apiLessons.toLessons(week)
|
||||||
|
if (convertErrors.isNotEmpty()) {
|
||||||
|
pushErrorNotification()
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
database.lessonDao().insert(*lessons.toTypedArray())
|
||||||
|
ScheduleWidget().updateAll(context)
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pushErrorNotification() {
|
||||||
|
notificationManager.notify(
|
||||||
|
notificationId,
|
||||||
|
NotificationCompat.Builder(context, channelId)
|
||||||
|
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||||
|
.setContentTitle(context.resources.getString(R.string.failed_get_schedule))
|
||||||
|
.setContentText(context.resources.getString(R.string.log_into_app_to_update))
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,4 +19,8 @@
|
|||||||
<string name="education_week">education week</string>
|
<string name="education_week">education week</string>
|
||||||
<string name="education_year">education year</string>
|
<string name="education_year">education year</string>
|
||||||
<string name="schedule_for_group">Schedule for the group</string>
|
<string name="schedule_for_group">Schedule for the group</string>
|
||||||
|
<string name="failed_get_schedule">Failed to get a schedule!</string>
|
||||||
|
<string name="log_into_app_to_update">Log into the app to update your schedule</string>
|
||||||
|
<string name="no_classes_today">No classes today</string>
|
||||||
|
<string name="no_classes">No classes</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -18,4 +18,8 @@
|
|||||||
<string name="education_week">учебная неделя</string>
|
<string name="education_week">учебная неделя</string>
|
||||||
<string name="education_year">учебный год</string>
|
<string name="education_year">учебный год</string>
|
||||||
<string name="schedule_for_group">Расписание для группы</string>
|
<string name="schedule_for_group">Расписание для группы</string>
|
||||||
|
<string name="failed_get_schedule">Не удалось получить расписание!</string>
|
||||||
|
<string name="log_into_app_to_update">Войдите в приложение, чтобы обновить расписание</string>
|
||||||
|
<string name="no_classes_today">Сегодня нет занятий</string>
|
||||||
|
<string name="no_classes">Нет занятий</string>
|
||||||
</resources>
|
</resources>
|
||||||
8
app/src/main/res/xml/widget_info.xml
Normal file
8
app/src/main/res/xml/widget_info.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
|
android:minWidth="288.0dp"
|
||||||
|
android:minHeight="216.0dp"
|
||||||
|
android:resizeMode="vertical|horizontal">
|
||||||
|
</appwidget-provider>
|
||||||
@@ -14,6 +14,7 @@ media3Common = "1.4.1"
|
|||||||
kotlinSerializationJson = "1.7.1"
|
kotlinSerializationJson = "1.7.1"
|
||||||
room = "2.6.1"
|
room = "2.6.1"
|
||||||
workRuntimeKtx = "2.9.1"
|
workRuntimeKtx = "2.9.1"
|
||||||
|
glance = "1.1.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -38,6 +39,8 @@ androidx-room-runtime = { group = "androidx.room", name = "room-runtime", versio
|
|||||||
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
|
||||||
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
|
||||||
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||||
|
androidx-glance = { group = "androidx.glance", name = "glance-appwidget", version.ref="glance" }
|
||||||
|
androidx-glance-material = { group = "androidx.glance", name = "glance-material3", version.ref="glance" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user