From 655a352dc61109cd50f4cc4fb8e573d4d804be19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:32:40 +0700 Subject: [PATCH 01/12] 2.0-draft19 (1493) - Update Project - Updated dependencies to latest. --- app/build.gradle | 8 +- .../dutschedule/activity/BaseActivity.kt | 9 +- .../dutschedule/activity/HelpActivity.kt | 4 +- .../activity/PermissionsActivity.kt | 4 +- .../dutschedule/activity/SettingsActivity.kt | 194 +++- ...ctFilterDialog.kt => AddASubjectFilter.kt} | 19 +- .../settings/DeleteASubjectFilterDialog.kt | 2 +- .../settings/DeleteAllSubjectFilterDialog.kt | 2 +- .../DialogAppBackgroundSettings.kt | 2 +- .../{dialog => }/DialogAppThemeSettings.kt | 2 +- .../{dialog => }/DialogSchoolYearSettings.kt | 9 +- .../dutschedule/ui/view/account/MainView.kt | 2 +- .../dutschedule/ui/view/account/SubjectFee.kt | 5 +- .../ui/view/account/SubjectInformation.kt | 5 +- .../ui/view/account/TrainingResult.kt | 4 +- .../ui/view/account/TrainingSubjectResult.kt | 2 +- .../ui/view/main/MainViewDashboard.kt | 12 +- .../ui/view/main/MainViewTabbed.kt | 12 +- .../dutschedule/ui/view/news/MainView.kt | 3 +- .../dutschedule/ui/view/news/NewsSearch.kt | 5 +- .../ui/view/settings/AboutApplication.kt | 71 ++ .../ui/view/settings/ExperimentSettings.kt | 76 +- .../ui/view/settings/LanguageSettings.kt | 29 +- .../dutschedule/ui/view/settings/MainView.kt | 51 +- .../view/settings/NewsNotificationSettings.kt | 830 ++++++++---------- .../settings/ParseNewsSubjectNotification.kt | 44 +- 26 files changed, 745 insertions(+), 661 deletions(-) rename app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/{AddNewSubjectFilterDialog.kt => AddASubjectFilter.kt} (90%) rename app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/{dialog => }/DialogAppBackgroundSettings.kt (96%) rename app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/{dialog => }/DialogAppThemeSettings.kt (98%) rename app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/{dialog => }/DialogSchoolYearSettings.kt (95%) create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt diff --git a/app/build.gradle b/app/build.gradle index 49347ec..440cbab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 34 - versionCode 1467 + versionCode 1493 versionName "2.0-draft19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -63,8 +63,8 @@ dependencies { implementation platform('androidx.compose:compose-bom:2024.06.00') implementation 'androidx.compose.ui:ui-graphics' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00') androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.6.8" androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00') @@ -82,7 +82,7 @@ dependencies { runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2' // https://mvnrepository.com/artifact/androidx.fragment/fragment-ktx - runtimeOnly 'androidx.fragment:fragment-ktx:1.8.0' + runtimeOnly 'androidx.fragment:fragment-ktx:1.8.1' // https://mvnrepository.com/artifact/androidx.compose.material3/material3 implementation 'androidx.compose.material3:material3:1.2.1' diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt index f70d333..82fd117 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt @@ -6,7 +6,6 @@ import android.graphics.Bitmap import android.net.Uri import android.os.Bundle import android.os.StrictMode -import android.window.SplashScreen import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent @@ -15,18 +14,15 @@ import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.Image import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.graphics.Color @@ -36,7 +32,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel @@ -181,9 +176,9 @@ abstract class BaseActivity: ComponentActivity() { return mainViewModel } - fun getControlBackgroundAlpha(): Float { + fun getBackgroundAlpha(): Float { return when (mainViewModel.appSettings.value.backgroundImage != BackgroundImageOption.None) { - true -> mainViewModel.appSettings.value.componentOpacity + true -> mainViewModel.appSettings.value.backgroundImageOpacity false -> 1f // true -> return mainViewModel.appSettings.value. } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt index 862ce87..8963d40 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt @@ -123,7 +123,7 @@ class HelpActivity : BaseActivity() { .padding(bottom = 7.dp) .clickable { clearAllFocusAndHideKeyboard() }, color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = getControlBackgroundAlpha() + alpha = getBackgroundAlpha() ), shape = RoundedCornerShape(7.dp), content = { @@ -190,7 +190,7 @@ class HelpActivity : BaseActivity() { HelpLinkClickable( item = item, modifier = Modifier.fillMaxWidth().padding(bottom = 7.dp), - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), linkClicked = { clearAllFocusAndHideKeyboard() try { diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt index 11ad0ac..bb70004 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt @@ -126,7 +126,7 @@ class PermissionsActivity : BaseActivity() { bottomBar = { BottomAppBar( containerColor = BottomAppBarDefaults.containerColor.copy( - alpha = getControlBackgroundAlpha() + alpha = getBackgroundAlpha() ), floatingActionButton = { ExtendedFloatingActionButton( @@ -164,7 +164,7 @@ class PermissionsActivity : BaseActivity() { isRequired = false, isGranted = item.isGranted, padding = PaddingValues(bottom = 10.dp), - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), clicked = { permissionRequest?.let { if (item.isGranted) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt index c150d79..363d408 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt @@ -1,6 +1,7 @@ package io.zoemeow.dutschedule.activity import android.content.Context +import android.content.Intent import android.util.Log import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -8,12 +9,13 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.settings.BackgroundImageOption -import io.zoemeow.dutschedule.ui.view.settings.ExperimentSettings -import io.zoemeow.dutschedule.ui.view.settings.LanguageSettings -import io.zoemeow.dutschedule.ui.view.settings.MainView -import io.zoemeow.dutschedule.ui.view.settings.NewsNotificationSettings -import io.zoemeow.dutschedule.ui.view.settings.ParseNewsSubjectNotification +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_AppLanguageSettings +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ExperimentSettings +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_NewsNotificationSettings +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ParseNewsSubjectNotification import io.zoemeow.dutschedule.utils.BackgroundImageUtil @AndroidEntryPoint @@ -65,47 +67,211 @@ class SettingsActivity : BaseActivity() { ) { when (intent.action) { INTENT_PARSENEWSSUBJECTNOTIFICATION -> { - ParseNewsSubjectNotification( + Activity_Settings_ParseNewsSubjectNotification( context = context, snackBarHostState = snackBarHostState, containerColor = containerColor, - contentColor = contentColor + contentColor = contentColor, + activity = this, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_EXPERIMENTSETTINGS -> { - ExperimentSettings( + Activity_Settings_ExperimentSettings( context = context, snackBarHostState = snackBarHostState, containerColor = containerColor, - contentColor = contentColor + contentColor = contentColor, + activity = this, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_LANGUAGESETTINGS -> { - LanguageSettings( + Activity_Settings_AppLanguageSettings( context = context, snackBarHostState = snackBarHostState, containerColor = containerColor, - contentColor = contentColor + contentColor = contentColor, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_NEWSNOTIFICATIONSETTINGS -> { - NewsNotificationSettings( + Activity_Settings_NewsNotificationSettings( context = context, snackBarHostState = snackBarHostState, containerColor = containerColor, - contentColor = contentColor + contentColor = contentColor, + onBack = { + setResult(RESULT_CANCELED) + finish() + }, + fetchNewsInBackgroundDuration = getMainViewModel().appSettings.value.newsBackgroundDuration, + onFetchNewsStateChanged = { duration -> + if (duration > 0) { + if (PermissionsActivity.checkPermissionScheduleExactAlarm(context).isGranted && PermissionsActivity.checkPermissionNotification(context).isGranted) { + // Fetch news in background onClick + val dataTemp = getMainViewModel().appSettings.value.clone( + fetchNewsBackgroundDuration = duration + ) + getMainViewModel().appSettings.value = dataTemp + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = context.getString( + R.string.settings_newsnotify_fetchnewsinbackground_enabled, + duration + ), + clearPrevious = true + ) + } else { + showSnackBar( + text = context.getString(R.string.settings_newsnotify_snackbar_missingpermissions), + clearPrevious = true, + actionText = context.getString(R.string.action_grant), + action = { + Intent(context, PermissionsActivity::class.java).also { intent -> + context.startActivity(intent) + } + } + ) + } + } else { + val dataTemp = getMainViewModel().appSettings.value.clone( + fetchNewsBackgroundDuration = 0 + ) + getMainViewModel().appSettings.value = dataTemp + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_disabled), + clearPrevious = true + ) + } + }, + isNewSubjectNotificationParseEnabled = getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, + onNewSubjectNotificationParseClick = { + Intent(context, SettingsActivity::class.java).apply { + action = INTENT_PARSENEWSSUBJECTNOTIFICATION + }.also { intent -> context.startActivity(intent) } + }, + isNewsGlobalEnabled = getMainViewModel().appSettings.value.newsBackgroundGlobalEnabled, + onNewsGlobalStateChanged = { enabled -> + val dataTemp = getMainViewModel().appSettings.value.clone( + newsBackgroundGlobalEnabled = enabled + ) + getMainViewModel().appSettings.value = dataTemp + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = when (enabled) { + true -> context.getString(R.string.settings_newsnotify_newsglobal_enabled) + false -> context.getString(R.string.settings_newsnotify_newsglobal_disabled) + }, + clearPrevious = true + ) + }, + isNewsSubjectEnabled = getMainViewModel().appSettings.value.newsBackgroundSubjectEnabled, + onNewsSubjectStateChanged = f@ { code -> + if (code == 1) { + showSnackBar( + text = "\"Match your subject schedule\" option is in development. Check back soon.", + clearPrevious = true + ) + return@f + } + + val dataTemp = getMainViewModel().appSettings.value.clone( + newsBackgroundSubjectEnabled = code + ) + getMainViewModel().appSettings.value = dataTemp + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = when (code) { + -1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_disabled) + 0 -> context.getString(R.string.settings_newsnotify_newssubject_notify_all) + 1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchsubsch) + 2 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchfilter) + // TODO: No code valid + else -> "----------" + }, + clearPrevious = true + ) + }, + subjectFilterList = getMainViewModel().appSettings.value.newsBackgroundFilterList, + onSubjectFilterAdd = { subjectCode -> + // Add a filter + try { + getMainViewModel().appSettings.value.newsBackgroundFilterList.add(subjectCode) + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = context.getString( + R.string.settings_newsnotify_newsfilter_notify_add, + subjectCode.subjectName, + subjectCode.studentYearId, + ".Nh", + subjectCode.classId + ), + clearPrevious = true + ) + } catch (_: Exception) { } + }, + onSubjectFilterDelete = { subjectCode -> + // Delete a filter + try { + val data = subjectCode.copy() + getMainViewModel().appSettings.value.newsBackgroundFilterList.remove(subjectCode) + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = context.getString( + R.string.settings_newsnotify_newsfilter_notify_delete, + data.subjectName, + data.studentYearId, + ".Nh", + data.classId + ), + clearPrevious = true + ) + } catch (_: Exception) { } + }, + onSubjectFilterClear = { + // Delete all filters + try { + getMainViewModel().appSettings.value.newsBackgroundFilterList.clear() + getMainViewModel().saveSettings(saveSettingsOnly = true) + showSnackBar( + text = context.getString(R.string.settings_newsnotify_newsfilter_notify_deleteall), + clearPrevious = true + ) + } catch (_: Exception) { } + }, + opacity = getBackgroundAlpha() ) } else -> { - MainView( + Activity_Settings( context = context, snackBarHostState = snackBarHostState, containerColor = containerColor, contentColor = contentColor, + componentBackgroundAlpha = getBackgroundAlpha(), + mainViewModel = getMainViewModel(), + onShowSnackBar = { text, clearPrevious, actionText, action -> + showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + }, mediaRequest = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddNewSubjectFilterDialog.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddASubjectFilter.kt similarity index 90% rename from app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddNewSubjectFilterDialog.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddASubjectFilter.kt index ab4afd1..428ccca 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddNewSubjectFilterDialog.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/AddASubjectFilter.kt @@ -19,11 +19,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.ui.component.base.DialogBase @Composable -fun SettingsActivity.AddNewSubjectFilterDialog( +fun Dialog_Settings_NewsNotificationSettings_Add( context: Context, isVisible: Boolean = false, onDismiss: (() -> Unit)? = null, @@ -33,6 +32,12 @@ fun SettingsActivity.AddNewSubjectFilterDialog( val classId = remember { mutableStateOf("") } val subjectName = remember { mutableStateOf("") } + fun clearAllTextField() { + schoolYearId.value = "" + classId.value = "" + subjectName.value = "" + } + DialogBase( modifier = Modifier.fillMaxWidth().padding(25.dp), title = context.getString(R.string.settings_newsnotify_newsfilter_dialogadd_title), @@ -96,12 +101,18 @@ fun SettingsActivity.AddNewSubjectFilterDialog( }, actionButtons = { TextButton( - onClick = { onDismiss?.let { it() } }, + onClick = { + onDismiss?.let { it() } + clearAllTextField() + }, content = { Text(context.getString(R.string.action_cancel)) }, modifier = Modifier.padding(start = 8.dp), ) TextButton( - onClick = { onDone?.let { it(schoolYearId.value, classId.value,subjectName.value) } }, + onClick = { onDone?.let { + it(schoolYearId.value, classId.value,subjectName.value) } + clearAllTextField() + }, content = { Text(context.getString(R.string.action_save)) }, modifier = Modifier.padding(start = 8.dp), ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteASubjectFilterDialog.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteASubjectFilterDialog.kt index 3964f14..47932e5 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteASubjectFilterDialog.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteASubjectFilterDialog.kt @@ -20,7 +20,7 @@ import io.zoemeow.dutschedule.model.settings.SubjectCode import io.zoemeow.dutschedule.ui.component.base.DialogBase @Composable -fun SettingsActivity.DeleteASubjectFilterDialog( +fun Dialog_Settings_NewsNotificationSettings_Delete( context: Context, subjectCode: SubjectCode = SubjectCode("", "", ""), isVisible: Boolean = false, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteAllSubjectFilterDialog.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteAllSubjectFilterDialog.kt index 838a36a..e9c7a2c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteAllSubjectFilterDialog.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DeleteAllSubjectFilterDialog.kt @@ -19,7 +19,7 @@ import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.ui.component.base.DialogBase @Composable -fun SettingsActivity.DeleteAllSubjectFilterDialog( +fun Dialog_Settings_NewsNotificationSettings_ClearAll( context: Context, isVisible: Boolean = false, onDismiss: (() -> Unit)? = null, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppBackgroundSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt similarity index 96% rename from app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppBackgroundSettings.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt index bd5c20e..ff8623a 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppBackgroundSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt @@ -1,4 +1,4 @@ -package io.zoemeow.dutschedule.ui.component.settings.dialog +package io.zoemeow.dutschedule.ui.component.settings import android.content.Context import android.os.Build diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppThemeSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppThemeSettings.kt similarity index 98% rename from app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppThemeSettings.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppThemeSettings.kt index c9374f9..6dae222 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogAppThemeSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppThemeSettings.kt @@ -1,4 +1,4 @@ -package io.zoemeow.dutschedule.ui.component.settings.dialog +package io.zoemeow.dutschedule.ui.component.settings import android.content.Context import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogSchoolYearSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt similarity index 95% rename from app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogSchoolYearSettings.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt index 7fa1302..fb8354a 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/dialog/DialogSchoolYearSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt @@ -1,4 +1,4 @@ -package io.zoemeow.dutschedule.ui.component.settings.dialog +package io.zoemeow.dutschedule.ui.component.settings import android.content.Context import android.util.Log @@ -12,15 +12,11 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Refresh import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -33,14 +29,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.dutwrapper.dutwrapper.Utils -import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.account.SchoolYearItem import io.zoemeow.dutschedule.ui.component.base.DataAdjuster import io.zoemeow.dutschedule.ui.component.base.DialogBase -import io.zoemeow.dutschedule.ui.component.base.OutlinedTextBox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt index 57759b2..da376cf 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt @@ -57,7 +57,7 @@ fun AccountActivity.MainView( snackBarHostState = snackBarHostState, containerColor = containerColor, contentColor = contentColor, - componentBackgroundAlpha = getControlBackgroundAlpha(), + componentBackgroundAlpha = getBackgroundAlpha(), mainViewModel = getMainViewModel(), onShowSnackBar = { text, clearPrevious, actionText, action -> showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt index 1d076bc..e6f3fec 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt @@ -2,7 +2,6 @@ package io.zoemeow.dutschedule.ui.view.account import android.app.Activity.RESULT_OK import android.content.Context -import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -12,8 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Refresh @@ -147,7 +144,7 @@ fun AccountActivity.SubjectFee( AccountSubjectFeeInformation( modifier = Modifier.padding(bottom = 10.dp), item = item, - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), onClick = { } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt index ed52c31..890e205 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt @@ -2,7 +2,6 @@ package io.zoemeow.dutschedule.ui.view.account import android.app.Activity.RESULT_CANCELED import android.content.Context -import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -12,8 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Refresh @@ -144,7 +141,7 @@ fun AccountActivity.SubjectInformation( SubjectInformation( modifier = Modifier.padding(bottom = 7.dp), item = item, - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), onClick = { subjectScheduleItem.value = item subjectDetailVisible.value = true diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt index d1552d2..5ffc557 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt @@ -137,7 +137,7 @@ fun AccountActivity.TrainingResult( title = context.getString(R.string.account_trainingstatus_trainbox_title), isTitleCentered = true, padding = PaddingValues(start = 10.dp, end = 10.dp, bottom = 7.dp), - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), content = { Column( modifier = Modifier @@ -215,7 +215,7 @@ fun AccountActivity.TrainingResult( title = context.getString(R.string.account_trainingstatus_graduatebox_title), isTitleCentered = true, padding = PaddingValues(horizontal = 10.dp), - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), content = { Column( modifier = Modifier diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt index 6308592..6be2c2d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt @@ -264,7 +264,7 @@ fun AccountActivity.TrainingSubjectResult( selectedSubject.value = subjectItem modalBottomSheetEnabled.value = true }, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index 64811da..aa0b451 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -92,7 +92,7 @@ fun MainActivity.MainViewDashboard( bottomBar = { BottomAppBar( containerColor = BottomAppBarDefaults.containerColor.copy( - alpha = getControlBackgroundAlpha() + alpha = getBackgroundAlpha() ), actions = { BadgedBox( @@ -237,7 +237,7 @@ fun MainActivity.MainViewDashboard( padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), isLoading = getMainViewModel().currentSchoolYearWeek.processState.value == ProcessState.Running, currentSchoolWeek = getMainViewModel().currentSchoolYearWeek.data.value, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) LessonTodaySummaryItem( padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), @@ -260,7 +260,7 @@ fun MainActivity.MainViewDashboard( schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson } }.toList(), - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) // AffectedLessonsSummaryItem( // padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), @@ -295,7 +295,7 @@ fun MainActivity.MainViewDashboard( context.startActivity(Intent(context, NewsActivity::class.java)) }, isLoading = getMainViewModel().newsInstance.newsGlobal.processState.value == ProcessState.Running, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) UpdateAvailableSummaryItem( padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), @@ -309,7 +309,7 @@ fun MainActivity.MainViewDashboard( customTab = false, ) }, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) }, ) @@ -372,7 +372,7 @@ fun MainActivity.MainViewDashboard( clearPrevious = true ) }, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) BackHandler(isNotificationOpened.value) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt index b4c03db..d4e49e2 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt @@ -36,7 +36,7 @@ import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.NavBarItem import io.zoemeow.dutschedule.ui.view.account.AccountMainView import io.zoemeow.dutschedule.ui.view.news.NewsMainView -import io.zoemeow.dutschedule.ui.view.settings.SettingsMainView +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings @Composable fun MainActivity.MainViewTabbed( @@ -108,7 +108,7 @@ fun MainActivity.MainViewTabbed( context = context, containerColor = containerColor, contentColor = contentColor, - componentBackgroundAlpha = getControlBackgroundAlpha(), + componentBackgroundAlpha = getBackgroundAlpha(), mainViewModel = getMainViewModel(), searchRequested = { val intent = Intent(context, NewsActivity::class.java) @@ -123,7 +123,7 @@ fun MainActivity.MainViewTabbed( context = context, containerColor = containerColor, contentColor = contentColor, - componentBackgroundAlpha = getControlBackgroundAlpha(), + componentBackgroundAlpha = getBackgroundAlpha(), mainViewModel = getMainViewModel(), onShowSnackBar = { text, clearPrevious, actionText, action -> showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) @@ -178,16 +178,16 @@ fun MainActivity.MainViewTabbed( clearPrevious = true ) }, - opacity = getControlBackgroundAlpha() + opacity = getBackgroundAlpha() ) } composable(NavBarItem.settings.route) { - SettingsMainView( + Activity_Settings( context = context, containerColor = containerColor, contentColor = contentColor, - componentBackgroundAlpha = getControlBackgroundAlpha(), + componentBackgroundAlpha = getBackgroundAlpha(), mainViewModel = getMainViewModel(), mediaRequest = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt index 01592b1..96e5256 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt @@ -1,7 +1,6 @@ package io.zoemeow.dutschedule.ui.view.news import android.app.Activity.RESULT_CANCELED -import android.app.Activity.RESULT_OK import android.content.Context import android.content.Intent import androidx.compose.foundation.ExperimentalFoundationApi @@ -70,7 +69,7 @@ fun NewsActivity.MainView( containerColor = containerColor, contentColor = contentColor, searchRequested = searchRequested, - componentBackgroundAlpha = getControlBackgroundAlpha(), + componentBackgroundAlpha = getBackgroundAlpha(), mainViewModel = getMainViewModel(), onBack = { setResult(RESULT_CANCELED) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt index 3ace9ef..94b5d97 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt @@ -1,10 +1,8 @@ package io.zoemeow.dutschedule.ui.view.news import android.app.Activity.RESULT_CANCELED -import android.app.Activity.RESULT_OK import android.content.Context import android.content.Intent -import androidx.activity.ComponentActivity import androidx.activity.compose.BackHandler import androidx.compose.animation.core.MutableTransitionState import androidx.compose.foundation.layout.fillMaxSize @@ -43,7 +41,6 @@ import com.google.gson.Gson import io.dutwrapper.dutwrapper.model.enums.NewsType import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity -import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.news.NewsSearchOptionAndHistory import io.zoemeow.dutschedule.ui.component.news.NewsSearchResult @@ -164,7 +161,7 @@ fun NewsActivity.NewsSearch( .padding(horizontal = 10.dp), newsList = newsSearchViewModel.newsList, lazyListState = lazyListState, - opacity = getControlBackgroundAlpha(), + opacity = getBackgroundAlpha(), processState = newsSearchViewModel.progress.value, onEndOfList = { newsSearchViewModel.invokeSearch() diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt new file mode 100644 index 0000000..65a24b5 --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt @@ -0,0 +1,71 @@ +package io.zoemeow.dutschedule.ui.view.settings + +import android.content.Context +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Activity_Settings_AboutApplication( + context: Context, + snackBarHostState: SnackbarHostState, + containerColor: Color, + contentColor: Color, + onBackRequested: () -> Unit, + backgroundOpacity: Float = 1f, + controlOpacity: Float = 1f +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + containerColor = containerColor, + contentColor = contentColor, + topBar = { + LargeTopAppBar( + title = { Text("About") }, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), + navigationIcon = { + IconButton( + onClick = { + onBackRequested() + }, + content = { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + context.getString(R.string.action_back), + modifier = Modifier.size(25.dp) + ) + } + ) + }, + scrollBehavior = scrollBehavior + ) + } + ) { paddingValues -> + // TODO: Add function here! + val d = paddingValues + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt index 81de122..33c9650 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt @@ -1,11 +1,9 @@ package io.zoemeow.dutschedule.ui.view.settings -import android.app.Activity.RESULT_CANCELED import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -18,54 +16,59 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.SettingsActivity +import io.zoemeow.dutschedule.activity.BaseActivity import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.ui.component.base.DividerItem import io.zoemeow.dutschedule.ui.component.base.OptionItem import io.zoemeow.dutschedule.ui.component.base.OptionSwitchItem import io.zoemeow.dutschedule.ui.component.settings.ContentRegion -import io.zoemeow.dutschedule.ui.component.settings.dialog.DialogSchoolYearSettings +import io.zoemeow.dutschedule.ui.component.settings.DialogSchoolYearSettings import java.util.Locale - @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsActivity.ExperimentSettings( +fun Activity_Settings_ExperimentSettings( context: Context, snackBarHostState: SnackbarHostState, containerColor: Color, - contentColor: Color + contentColor: Color, + activity: BaseActivity, + onBack: () -> Unit ) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val dialogSchoolYear = remember { mutableStateOf(false) } Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = containerColor, contentColor = contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.settings_experiment_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { IconButton( onClick = { - setResult(RESULT_CANCELED) - finish() + onBack() }, content = { Icon( @@ -75,7 +78,8 @@ fun SettingsActivity.ExperimentSettings( ) } ) - } + }, + scrollBehavior = scrollBehavior ) }, content = { @@ -94,14 +98,14 @@ fun SettingsActivity.ExperimentSettings( title = context.getString(R.string.settings_experiment_option_currentschyear), description = context.getString( R.string.settings_experiment_option_currentschyear_description, - getMainViewModel().appSettings.value.currentSchoolYear.year, - getMainViewModel().appSettings.value.currentSchoolYear.year + 1, - when (getMainViewModel().appSettings.value.currentSchoolYear.semester) { + activity.getMainViewModel().appSettings.value.currentSchoolYear.year, + activity.getMainViewModel().appSettings.value.currentSchoolYear.year + 1, + when (activity.getMainViewModel().appSettings.value.currentSchoolYear.semester) { 1 -> "1" 2 -> "2" else -> "2" }, - if (getMainViewModel().appSettings.value.currentSchoolYear.semester > 2) " ${context.getString(R.string.settings_experiment_option_currentschyear_insummer)}" else "" + if (activity.getMainViewModel().appSettings.value.currentSchoolYear.semester > 2) " ${context.getString(R.string.settings_experiment_option_currentschyear_insummer)}" else "" ), onClick = { dialogSchoolYear.value = true @@ -121,13 +125,13 @@ fun SettingsActivity.ExperimentSettings( description = String.format( Locale.ROOT, "%2.0f%% %s", - (getMainViewModel().appSettings.value.backgroundImageOpacity * 100), - if (getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { + (activity.getMainViewModel().appSettings.value.backgroundImageOpacity * 100), + if (activity.getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" } else "" ), onClick = { - showSnackBar(context.getString(R.string.feature_not_ready), true) + activity.showSnackBar(context.getString(R.string.feature_not_ready), true) /* TODO: Implement here: Background opacity */ } ) @@ -137,13 +141,13 @@ fun SettingsActivity.ExperimentSettings( description = String.format( Locale.ROOT, "%2.0f%% %s", - (getMainViewModel().appSettings.value.componentOpacity * 100), - if (getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { + (activity.getMainViewModel().appSettings.value.componentOpacity * 100), + if (activity.getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" } else "" ), onClick = { - showSnackBar(context.getString(R.string.feature_not_ready), true) + activity.showSnackBar(context.getString(R.string.feature_not_ready), true) /* TODO: Implement here: Component opacity */ } ) @@ -153,16 +157,16 @@ fun SettingsActivity.ExperimentSettings( title = context.getString(R.string.settings_experiment_option_dashboardview), isVisible = true, isEnabled = true, - isChecked = getMainViewModel().appSettings.value.mainScreenDashboardView, - description = when (getMainViewModel().appSettings.value.mainScreenDashboardView) { + isChecked = activity.getMainViewModel().appSettings.value.mainScreenDashboardView, + description = when (activity.getMainViewModel().appSettings.value.mainScreenDashboardView) { true -> context.getString(R.string.settings_experiment_option_dashboardview_choice_enabled) false -> context.getString(R.string.settings_experiment_option_dashboardview_choice_disabled) }, onValueChanged = { - showSnackBar( + activity.showSnackBar( text = context.getString( R.string.settings_experiment_option_dashboardview_warning, - when (getMainViewModel().appSettings.value.mainScreenDashboardView) { + when (activity.getMainViewModel().appSettings.value.mainScreenDashboardView) { true -> context.getString(R.string.settings_experiment_option_dashboardview_warning_disable) false -> context.getString(R.string.settings_experiment_option_dashboardview_warning_enable) } @@ -170,10 +174,10 @@ fun SettingsActivity.ExperimentSettings( clearPrevious = true, actionText = context.getString(R.string.action_confirm), action = { - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - mainScreenDashboardView = !getMainViewModel().appSettings.value.mainScreenDashboardView + activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( + mainScreenDashboardView = !activity.getMainViewModel().appSettings.value.mainScreenDashboardView ) - getMainViewModel().saveSettings( + activity.getMainViewModel().saveSettings( onCompleted = { val packageManager: PackageManager = context.packageManager val intent: Intent = packageManager.getLaunchIntentForPackage(context.packageName)!! @@ -200,7 +204,7 @@ fun SettingsActivity.ExperimentSettings( title = context.getString(R.string.settings_experiment_option_debuglog), description = context.getString(R.string.settings_experiment_option_debuglog_description), onClick = { - showSnackBar(context.getString(R.string.feature_not_ready), true) + activity.showSnackBar(context.getString(R.string.feature_not_ready), true) /* TODO: Implement here: Debug log */ } ) @@ -214,12 +218,12 @@ fun SettingsActivity.ExperimentSettings( context = context, isVisible = dialogSchoolYear.value, dismissRequested = { dialogSchoolYear.value = false }, - currentSchoolYearItem = getMainViewModel().appSettings.value.currentSchoolYear, + currentSchoolYearItem = activity.getMainViewModel().appSettings.value.currentSchoolYear, onSubmit = { - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( + activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( currentSchoolYear = it ) - getMainViewModel().saveSettings() + activity.getMainViewModel().saveSettings() dialogSchoolYear.value = false } ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt index b763d76..ce99ff2 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt @@ -1,12 +1,10 @@ package io.zoemeow.dutschedule.ui.view.settings -import android.app.Activity.RESULT_CANCELED import android.app.LocaleManager import android.content.Context import android.os.Build import android.os.LocaleList import android.util.Log -import androidx.activity.ComponentActivity import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -25,49 +23,55 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.os.LocaleListCompat import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.SettingsActivity import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsActivity.LanguageSettings( +fun Activity_Settings_AppLanguageSettings( context: Context, snackBarHostState: SnackbarHostState, containerColor: Color, - contentColor: Color + contentColor: Color, + onBack: () -> Unit ) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = containerColor, contentColor = contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.settings_applanguage_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { IconButton( onClick = { - setResult(RESULT_CANCELED) - finish() + onBack() }, content = { Icon( @@ -99,7 +103,8 @@ fun SettingsActivity.LanguageSettings( ) } ) - } + }, + scrollBehavior = scrollBehavior ) }, content = { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt index 89e5587..1de3225 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.settings -import android.app.Activity.RESULT_CANCELED import android.content.Context import android.content.Intent import android.net.Uri @@ -20,11 +19,11 @@ import androidx.compose.material.icons.filled.Notifications import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -33,6 +32,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.BuildConfig @@ -46,41 +46,15 @@ import io.zoemeow.dutschedule.ui.component.base.DividerItem import io.zoemeow.dutschedule.ui.component.base.OptionItem import io.zoemeow.dutschedule.ui.component.base.OptionSwitchItem import io.zoemeow.dutschedule.ui.component.settings.ContentRegion -import io.zoemeow.dutschedule.ui.component.settings.dialog.DialogAppBackgroundSettings -import io.zoemeow.dutschedule.ui.component.settings.dialog.DialogAppThemeSettings +import io.zoemeow.dutschedule.ui.component.settings.DialogAppBackgroundSettings +import io.zoemeow.dutschedule.ui.component.settings.DialogAppThemeSettings import io.zoemeow.dutschedule.utils.openLink import io.zoemeow.dutschedule.viewmodel.MainViewModel import java.util.Locale -@Composable -fun SettingsActivity.MainView( - context: Context, - snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - mediaRequest: () -> Unit -) { - SettingsMainView( - context = context, - snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getControlBackgroundAlpha(), - mainViewModel = getMainViewModel(), - mediaRequest = mediaRequest, - onShowSnackBar = { text, clearPrevious, actionText, action -> - showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) - }, - onBack = { - setResult(RESULT_CANCELED) - finish() - } - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsMainView( +fun Activity_Settings( context: Context, snackBarHostState: SnackbarHostState? = null, containerColor: Color, @@ -91,18 +65,24 @@ fun SettingsMainView( onShowSnackBar: ((String, Boolean, String?, (() -> Unit)?) -> Unit)? = null, onBack: (() -> Unit)? = null ) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val dialogAppTheme: MutableState = remember { mutableStateOf(false) } val dialogBackground: MutableState = remember { mutableStateOf(false) } Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, containerColor = containerColor, contentColor = contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.settings_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { if (onBack != null) { IconButton( @@ -118,7 +98,8 @@ fun SettingsMainView( } ) } - } + }, + scrollBehavior = scrollBehavior ) }, content = { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt index f20ea37..7242a19 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt @@ -1,8 +1,6 @@ package io.zoemeow.dutschedule.ui.view.settings -import android.app.Activity.RESULT_OK import android.content.Context -import android.content.Intent import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -27,6 +25,7 @@ import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider @@ -35,7 +34,6 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SuggestionChip import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -45,12 +43,10 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.PermissionsActivity -import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.settings.SubjectCode import io.zoemeow.dutschedule.ui.component.base.CheckboxOption import io.zoemeow.dutschedule.ui.component.base.DividerItem @@ -58,27 +54,46 @@ import io.zoemeow.dutschedule.ui.component.base.OptionItem import io.zoemeow.dutschedule.ui.component.base.RadioButtonOption import io.zoemeow.dutschedule.ui.component.base.SimpleCardItem import io.zoemeow.dutschedule.ui.component.base.SwitchWithTextInSurface -import io.zoemeow.dutschedule.ui.component.settings.AddNewSubjectFilterDialog +import io.zoemeow.dutschedule.ui.component.settings.Dialog_Settings_NewsNotificationSettings_Add import io.zoemeow.dutschedule.ui.component.settings.ContentRegion -import io.zoemeow.dutschedule.ui.component.settings.DeleteASubjectFilterDialog -import io.zoemeow.dutschedule.ui.component.settings.DeleteAllSubjectFilterDialog +import io.zoemeow.dutschedule.ui.component.settings.Dialog_Settings_NewsNotificationSettings_ClearAll +import io.zoemeow.dutschedule.ui.component.settings.Dialog_Settings_NewsNotificationSettings_Delete -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) @Composable -fun SettingsActivity.NewsNotificationSettings( +fun Activity_Settings_NewsNotificationSettings( context: Context, snackBarHostState: SnackbarHostState?, containerColor: Color, - contentColor: Color + contentColor: Color, + onBack: () -> Unit, + fetchNewsInBackgroundDuration: Int = 0, + onFetchNewsStateChanged: ((Int) -> Unit)? = null, + isNewSubjectNotificationParseEnabled: Boolean = false, + onNewSubjectNotificationParseClick: (() -> Unit)? = null, + isNewsGlobalEnabled: Boolean = false, + onNewsGlobalStateChanged: ((Boolean) -> Unit)? = null, + isNewsSubjectEnabled: Int = -1, + onNewsSubjectStateChanged: ((Int) -> Unit)? = null, + subjectFilterList: ArrayList = arrayListOf(), + onSubjectFilterAdd: ((SubjectCode) -> Unit)? = null, + onSubjectFilterDelete: ((SubjectCode) -> Unit)? = null, + onSubjectFilterClear: (() -> Unit)? = null, + opacity: Float = 1f ) { - // val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val durationTemp = remember { + mutableIntStateOf(fetchNewsInBackgroundDuration) + } + val dialogAddNew = remember { mutableStateOf(false) } val tempDeleteItem: MutableState = remember { mutableStateOf(SubjectCode("","","")) } val dialogDeleteItem = remember { mutableStateOf(false) } val dialogDeleteAll = remember { mutableStateOf(false) } Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) @@ -87,15 +102,16 @@ fun SettingsActivity.NewsNotificationSettings( containerColor = containerColor, contentColor = contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.settings_newsnotify_title)) }, - // colors = TopAppBarDefaults.largeTopAppBarColors(containerColor = Color.Transparent, scrolledContainerColor = Color.Transparent), - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent, scrolledContainerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { IconButton( onClick = { - setResult(RESULT_OK) - finish() + onBack() }, content = { Icon( @@ -105,121 +121,307 @@ fun SettingsActivity.NewsNotificationSettings( ) } ) + }, + scrollBehavior = scrollBehavior + ) + } + ) { paddingValues -> + Column( + modifier = Modifier.fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { + SwitchWithTextInSurface( + text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground), + enabled = true, + checked = fetchNewsInBackgroundDuration > 0, + onCheckedChange = { + // Refresh news state changed, default is 30 minutes + onFetchNewsStateChanged?.let { it(when { + (fetchNewsInBackgroundDuration > 0) -> 0 + else -> 30 + }) } } - // scrollBehavior = scrollBehavior ) - }, - ) { - MainView( - context = context, - padding = it, - fetchNewsInBackgroundDuration = getMainViewModel().appSettings.value.newsBackgroundDuration, - onFetchNewsStateChanged = { duration -> - if (duration > 0) { - if (PermissionsActivity.checkPermissionScheduleExactAlarm(context).isGranted && PermissionsActivity.checkPermissionNotification(context).isGranted) { - // Fetch news in background onClick - val dataTemp = getMainViewModel().appSettings.value.clone( - fetchNewsBackgroundDuration = duration - ) - getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = context.getString( - R.string.settings_newsnotify_fetchnewsinbackground_enabled, - duration - ), - clearPrevious = true - ) - } else { - showSnackBar( - text = context.getString(R.string.settings_newsnotify_snackbar_missingpermissions), - clearPrevious = true, - actionText = context.getString(R.string.action_grant), - action = { - Intent(context, PermissionsActivity::class.java).also { intent -> - context.startActivity(intent) + ContentRegion( + modifier = Modifier.padding(top = 10.dp), + textModifier = Modifier.padding(horizontal = 20.dp), + text = context.getString(R.string.settings_newsnotify_category_notification) + ) { + SimpleCardItem( + padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), + title = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_duration), + opacity = opacity, + content = { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 15.dp) + .padding(top = 5.dp, bottom = 10.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + context.getString( + R.string.settings_newsnotify_fetchnewsinbackground_value, + when (fetchNewsInBackgroundDuration) { + 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_value_disabled) + 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_value_enabled1) + else -> context.getString( + R.string.settings_newsnotify_fetchnewsinbackground_value_enabled2, + fetchNewsInBackgroundDuration + ) + } + ), + modifier = Modifier.padding(bottom = 10.dp) + ) + Slider( + valueRange = 5f..240f, + steps = 236, + value = durationTemp.intValue.toFloat(), + enabled = fetchNewsInBackgroundDuration > 0, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + activeTrackColor = MaterialTheme.colorScheme.primary, + inactiveTickColor = Color.Transparent, + inactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.4f) + ), + onValueChange = { + durationTemp.intValue = it.toInt() } - } - ) + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + content = { + Text( + when (durationTemp.intValue) { + 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_disabled) + 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_enabled1) + else -> context.getString( + R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_enabled2, + durationTemp.intValue + ) + } + ) + } + ) + FlowRow( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(top = 7.dp), + horizontalArrangement = Arrangement.Center, + content = { + listOf(15, 30, 60).forEach { min -> + SuggestionChip( + modifier = Modifier.padding(horizontal = 5.dp), + icon = { + if (durationTemp.intValue == min) { + Icon( + Icons.Default.Check, + context.getString(R.string.tooltip_selected), + modifier = Modifier.size(20.dp) + ) + } + }, + onClick = { + if (fetchNewsInBackgroundDuration > 0) { + durationTemp.intValue = min + } + }, + label = { + Text(when (min) { + 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_option_turnoff) + 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_option_value1) + else -> context.getString( + R.string.settings_newsnotify_fetchnewsinbackground_option_value2, + min + ) + }) + } + ) + } + } + ) + Spacer(modifier = Modifier.size(5.dp)) + ElevatedButton( + onClick = { + if (fetchNewsInBackgroundDuration > 0) { + onFetchNewsStateChanged?.let { it(durationTemp.intValue) } + } + }, + content = { + Text(context.getString(R.string.action_save)) + } + ) + } } - } else { - val dataTemp = getMainViewModel().appSettings.value.clone( - fetchNewsBackgroundDuration = 0 - ) - getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_disabled), - clearPrevious = true - ) - } - }, - isNewSubjectNotificationParseEnabled = getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, - onNewSubjectNotificationParseClick = { - Intent(context, SettingsActivity::class.java).apply { - action = SettingsActivity.INTENT_PARSENEWSSUBJECTNOTIFICATION - }.also { intent -> context.startActivity(intent) } - }, - isNewsGlobalEnabled = getMainViewModel().appSettings.value.newsBackgroundGlobalEnabled, - onNewsGlobalStateChanged = { enabled -> - val dataTemp = getMainViewModel().appSettings.value.clone( - newsBackgroundGlobalEnabled = enabled ) - getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = when (enabled) { - true -> context.getString(R.string.settings_newsnotify_newsglobal_enabled) - false -> context.getString(R.string.settings_newsnotify_newsglobal_disabled) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + title = context.getString(R.string.settings_parsenewssubject_title), + description = when (isNewSubjectNotificationParseEnabled) { + true -> context.getString(R.string.settings_newsnotify_parsenewssubject_enabled) + false -> context.getString(R.string.settings_newsnotify_parsenewssubject_disabled) }, - clearPrevious = true + onClick = { onNewSubjectNotificationParseClick?.let { it() } } ) - }, - isNewsSubjectEnabled = getMainViewModel().appSettings.value.newsBackgroundSubjectEnabled, - onNewsSubjectStateChanged = f@ { code -> - if (code == 1) { - showSnackBar( - text = "\"Match your subject schedule\" option is in development. Check back soon.", - clearPrevious = true - ) - return@f - } - - val dataTemp = getMainViewModel().appSettings.value.clone( - newsBackgroundSubjectEnabled = code + } + DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) + ContentRegion( + textModifier = Modifier.padding(horizontal = 20.dp), + text = context.getString(R.string.settings_newsnotify_newsglobal_title) + ) { + CheckboxOption( + title = context.getString(R.string.settings_newsnotify_newsglobal_enable), + modifierInside = Modifier.padding(horizontal = 6.5.dp), + isEnabled = fetchNewsInBackgroundDuration > 0, + isChecked = isNewsGlobalEnabled, + onClick = { + // Refresh news state changed + onNewsGlobalStateChanged?.let { it(!isNewsGlobalEnabled) } + } ) - getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = when (code) { - -1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_disabled) - 0 -> context.getString(R.string.settings_newsnotify_newssubject_notify_all) - 1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchsubsch) - 2 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchfilter) - // TODO: No code valid - else -> "----------" - }, - clearPrevious = true + } + ContentRegion( + modifier = Modifier.padding(top = 10.dp), + textModifier = Modifier.padding(horizontal = 20.dp), + text = context.getString(R.string.settings_newsnotify_newssubject_title) + ) { + RadioButtonOption( + modifierInside = Modifier.padding(horizontal = 6.5.dp), + title = context.getString(R.string.settings_newsnotify_newssubject_disabled), + isEnabled = fetchNewsInBackgroundDuration > 0, + isChecked = isNewsSubjectEnabled == -1, + onClick = { + // Subject news notification off - onClick + onNewsSubjectStateChanged?.let { it(-1) } + } + ) + RadioButtonOption( + modifierInside = Modifier.padding(horizontal = 6.5.dp), + title = context.getString(R.string.settings_newsnotify_newssubject_all), + isEnabled = fetchNewsInBackgroundDuration > 0, + isChecked = isNewsSubjectEnabled == 0, + onClick = { + // Subject news notification all - onClick + onNewsSubjectStateChanged?.let { it(0) } + } + ) + RadioButtonOption( + modifierInside = Modifier.padding(horizontal = 6.5.dp), + title = context.getString(R.string.settings_newsnotify_newssubject_matchsubsch), + isEnabled = fetchNewsInBackgroundDuration > 0, + isChecked = isNewsSubjectEnabled == 1, + onClick = { + // Subject news notification your subject schedule - onClick + onNewsSubjectStateChanged?.let { it(1) } + } ) - }, - subjectFilterList = getMainViewModel().appSettings.value.newsBackgroundFilterList, - onSubjectFilterAdd = { - // Add a filter - dialogAddNew.value = true - }, - onSubjectFilterDelete = { data -> - // Delete a filter - tempDeleteItem.value = data - dialogDeleteItem.value = true - }, - onSubjectFilterClear = { - // Delete all filters - dialogDeleteAll.value = true - }, - opacity = getControlBackgroundAlpha() - ) + RadioButtonOption( + modifierInside = Modifier.padding(horizontal = 6.5.dp), + title = context.getString(R.string.settings_newsnotify_newssubject_matchfilter), + isEnabled = fetchNewsInBackgroundDuration > 0, + isChecked = isNewsSubjectEnabled == 2, + onClick = { + // Subject news notification custom list - onClick + onNewsSubjectStateChanged?.let { it(2) } + } + ) + } + DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) + ContentRegion( + textModifier = Modifier.padding(horizontal = 20.dp), + text = context.getString(R.string.settings_newsnotify_newsfilter_title) + ) { + if (isNewsSubjectEnabled != 2) { + SimpleCardItem( + padding = PaddingValues(horizontal = 20.4.dp, vertical = 7.dp), + title = context.getString(R.string.settings_newsnotify_newsfilter_disabledwarning_title), + content = { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 15.dp) + .padding(bottom = 15.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + Text(context.getString(R.string.settings_newsnotify_newsfilter_disabledwarning_description)) + } + }, + opacity = opacity + ) + } else { + SimpleCardItem( + padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), + title = context.getString(R.string.settings_newsnotify_newsfilter_list_title), + opacity = opacity, + content = { + Column( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 15.dp) + .padding(bottom = 15.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.Start + ) { + if (subjectFilterList.size == 0) { + Text(context.getString(R.string.settings_newsnotify_newsfilter_list_nofilters)) + } + subjectFilterList.forEach { code -> + OptionItem( + modifier = Modifier.padding(vertical = 3.dp), + modifierInside = Modifier, + title = "${code.subjectName} [${code.studentYearId}.Nh${code.classId}]", + onClick = { }, + trailingIcon = { + IconButton( + onClick = { + if (fetchNewsInBackgroundDuration > 0) { + tempDeleteItem.value = code + dialogDeleteItem.value = true + } + }, + content = { + Icon(Icons.Default.Delete, context.getString(R.string.action_delete)) + } + ) + } + ) + } + } + } + ) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + title = context.getString(R.string.settings_newsnotify_newsfilter_add), + leadingIcon = { Icon(Icons.Default.Add, context.getString(R.string.settings_newsnotify_newsfilter_add)) }, + isEnabled = isNewsSubjectEnabled == 2, + onClick = { + // Add a subject news filter + dialogAddNew.value = true + } + ) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + title = context.getString(R.string.settings_newsnotify_newsfilter_deleteall), + leadingIcon = { Icon(Icons.Default.Delete, context.getString(R.string.settings_newsnotify_newsfilter_deleteall)) }, + isEnabled = isNewsSubjectEnabled == 2, + onClick = { + // Clear all subject news filter list + dialogDeleteAll.value = true + } + ) + } + } + } } - AddNewSubjectFilterDialog( + Dialog_Settings_NewsNotificationSettings_Add( context = context, isVisible = dialogAddNew.value, onDismiss = { dialogAddNew.value = false }, @@ -227,24 +429,13 @@ fun SettingsActivity.NewsNotificationSettings( // Add item manually try { val item = SubjectCode(syId, cId, subName) - getMainViewModel().appSettings.value.newsBackgroundFilterList.add(item) - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = context.getString( - R.string.settings_newsnotify_newsfilter_notify_add, - subName, - syId, - ".Nh", - cId - ), - clearPrevious = true - ) + onSubjectFilterAdd?.let { it(item) } } catch (_: Exception) { } dialogAddNew.value = false } ) - DeleteASubjectFilterDialog( + Dialog_Settings_NewsNotificationSettings_Delete( context = context, subjectCode = tempDeleteItem.value, isVisible = dialogDeleteItem.value, @@ -252,39 +443,25 @@ fun SettingsActivity.NewsNotificationSettings( onDone = { // Clear item on tempDeleteItem.value try { - getMainViewModel().appSettings.value.newsBackgroundFilterList.remove(tempDeleteItem.value) - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = context.getString( - R.string.settings_newsnotify_newsfilter_notify_delete, - tempDeleteItem.value.subjectName, - tempDeleteItem.value.studentYearId, - ".Nh", - tempDeleteItem.value.classId - ), - clearPrevious = true - ) + onSubjectFilterDelete?.let { it(tempDeleteItem.value) } } catch (_: Exception) { } dialogDeleteItem.value = false } ) - DeleteAllSubjectFilterDialog( + Dialog_Settings_NewsNotificationSettings_ClearAll( context = context, isVisible = dialogDeleteAll.value, onDismiss = { dialogDeleteAll.value = false }, onDone = { // Clear all items try { - getMainViewModel().appSettings.value.newsBackgroundFilterList.clear() - getMainViewModel().saveSettings(saveSettingsOnly = true) - showSnackBar( - text = context.getString(R.string.settings_newsnotify_newsfilter_notify_deleteall), - clearPrevious = true - ) + onSubjectFilterClear?.let { it() } } catch (_: Exception) { } + dialogDeleteAll.value = false } + ) BackHandler(dialogAddNew.value || dialogDeleteItem.value || dialogDeleteAll.value) { if (dialogAddNew.value) { @@ -299,336 +476,19 @@ fun SettingsActivity.NewsNotificationSettings( } } -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun MainView( - context: Context, - padding: PaddingValues = PaddingValues(0.dp), - fetchNewsInBackgroundDuration: Int = 0, - onFetchNewsStateChanged: ((Int) -> Unit)? = null, - isNewSubjectNotificationParseEnabled: Boolean = false, - onNewSubjectNotificationParseClick: (() -> Unit)? = null, - isNewsGlobalEnabled: Boolean = false, - onNewsGlobalStateChanged: ((Boolean) -> Unit)? = null, - isNewsSubjectEnabled: Int = -1, - onNewsSubjectStateChanged: ((Int) -> Unit)? = null, - subjectFilterList: ArrayList = arrayListOf(), - onSubjectFilterAdd: (() -> Unit)? = null, - onSubjectFilterDelete: ((SubjectCode) -> Unit)? = null, - onSubjectFilterClear: (() -> Unit)? = null, - opacity: Float = 1f -) { - val durationTemp = remember { - mutableIntStateOf(fetchNewsInBackgroundDuration) - } - - Column( - modifier = Modifier - .padding(padding) - .verticalScroll(rememberScrollState()) - ) { - SwitchWithTextInSurface( - text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground), - enabled = true, - checked = fetchNewsInBackgroundDuration > 0, - onCheckedChange = { - // Refresh news state changed, default is 30 minutes - onFetchNewsStateChanged?.let { it(when { - (fetchNewsInBackgroundDuration > 0) -> 0 - else -> 30 - }) } - } - ) - ContentRegion( - modifier = Modifier.padding(top = 10.dp), - textModifier = Modifier.padding(horizontal = 20.dp), - text = context.getString(R.string.settings_newsnotify_category_notification) - ) { - SimpleCardItem( - padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), - title = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_duration), - opacity = opacity, - content = { - Column( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(horizontal = 15.dp) - .padding(top = 5.dp, bottom = 10.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - context.getString( - R.string.settings_newsnotify_fetchnewsinbackground_value, - when (fetchNewsInBackgroundDuration) { - 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_value_disabled) - 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_value_enabled1) - else -> context.getString( - R.string.settings_newsnotify_fetchnewsinbackground_value_enabled2, - fetchNewsInBackgroundDuration - ) - } - ), - modifier = Modifier.padding(bottom = 10.dp) - ) - Slider( - valueRange = 5f..240f, - steps = 236, - value = durationTemp.intValue.toFloat(), - enabled = fetchNewsInBackgroundDuration > 0, - colors = SliderDefaults.colors( - activeTickColor = Color.Transparent, - activeTrackColor = MaterialTheme.colorScheme.primary, - inactiveTickColor = Color.Transparent, - inactiveTrackColor = MaterialTheme.colorScheme.secondary.copy(alpha = 0.4f) - ), - onValueChange = { - durationTemp.intValue = it.toInt() - } - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - content = { - Text( - when (durationTemp.intValue) { - 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_disabled) - 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_enabled1) - else -> context.getString( - R.string.settings_newsnotify_fetchnewsinbackground_modifiedvalue_enabled2, - durationTemp.intValue - ) - } - ) - } - ) - FlowRow( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(top = 7.dp), - horizontalArrangement = Arrangement.Center, - content = { - listOf(15, 30, 60).forEach { min -> - SuggestionChip( - modifier = Modifier.padding(horizontal = 5.dp), - icon = { - if (durationTemp.intValue == min) { - Icon( - Icons.Default.Check, - context.getString(R.string.tooltip_selected), - modifier = Modifier.size(20.dp) - ) - } - }, - onClick = { - if (fetchNewsInBackgroundDuration > 0) { - durationTemp.intValue = min - } - }, - label = { - Text(when (min) { - 0 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_option_turnoff) - 1 -> context.getString(R.string.settings_newsnotify_fetchnewsinbackground_option_value1) - else -> context.getString( - R.string.settings_newsnotify_fetchnewsinbackground_option_value2, - min - ) - }) - } - ) - } - } - ) - Spacer(modifier = Modifier.size(5.dp)) - ElevatedButton( - onClick = { - if (fetchNewsInBackgroundDuration > 0) { - onFetchNewsStateChanged?.let { it(durationTemp.intValue) } - } - }, - content = { - Text(context.getString(R.string.action_save)) - } - ) - } - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - title = context.getString(R.string.settings_parsenewssubject_title), - description = when (isNewSubjectNotificationParseEnabled) { - true -> context.getString(R.string.settings_newsnotify_parsenewssubject_enabled) - false -> context.getString(R.string.settings_newsnotify_parsenewssubject_disabled) - }, - onClick = { onNewSubjectNotificationParseClick?.let { it() } } - ) - } - DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) - ContentRegion( - textModifier = Modifier.padding(horizontal = 20.dp), - text = context.getString(R.string.settings_newsnotify_newsglobal_title) - ) { - CheckboxOption( - title = context.getString(R.string.settings_newsnotify_newsglobal_enable), - modifierInside = Modifier.padding(horizontal = 6.5.dp), - isEnabled = fetchNewsInBackgroundDuration > 0, - isChecked = isNewsGlobalEnabled, - onClick = { - // Refresh news state changed - onNewsGlobalStateChanged?.let { it(!isNewsGlobalEnabled) } - } - ) - } - ContentRegion( - modifier = Modifier.padding(top = 10.dp), - textModifier = Modifier.padding(horizontal = 20.dp), - text = context.getString(R.string.settings_newsnotify_newssubject_title) - ) { - RadioButtonOption( - modifierInside = Modifier.padding(horizontal = 6.5.dp), - title = context.getString(R.string.settings_newsnotify_newssubject_disabled), - isEnabled = fetchNewsInBackgroundDuration > 0, - isChecked = isNewsSubjectEnabled == -1, - onClick = { - // Subject news notification off - onClick - onNewsSubjectStateChanged?.let { it(-1) } - } - ) - RadioButtonOption( - modifierInside = Modifier.padding(horizontal = 6.5.dp), - title = context.getString(R.string.settings_newsnotify_newssubject_all), - isEnabled = fetchNewsInBackgroundDuration > 0, - isChecked = isNewsSubjectEnabled == 0, - onClick = { - // Subject news notification all - onClick - onNewsSubjectStateChanged?.let { it(0) } - } - ) - RadioButtonOption( - modifierInside = Modifier.padding(horizontal = 6.5.dp), - title = context.getString(R.string.settings_newsnotify_newssubject_matchsubsch), - isEnabled = fetchNewsInBackgroundDuration > 0, - isChecked = isNewsSubjectEnabled == 1, - onClick = { - // Subject news notification your subject schedule - onClick - onNewsSubjectStateChanged?.let { it(1) } - } - ) - RadioButtonOption( - modifierInside = Modifier.padding(horizontal = 6.5.dp), - title = context.getString(R.string.settings_newsnotify_newssubject_matchfilter), - isEnabled = fetchNewsInBackgroundDuration > 0, - isChecked = isNewsSubjectEnabled == 2, - onClick = { - // Subject news notification custom list - onClick - onNewsSubjectStateChanged?.let { it(2) } - } - ) - } - DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) - ContentRegion( - textModifier = Modifier.padding(horizontal = 20.dp), - text = context.getString(R.string.settings_newsnotify_newsfilter_title) - ) { - if (isNewsSubjectEnabled != 2) { - SimpleCardItem( - padding = PaddingValues(horizontal = 20.4.dp, vertical = 7.dp), - title = context.getString(R.string.settings_newsnotify_newsfilter_disabledwarning_title), - content = { - Column( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(horizontal = 15.dp) - .padding(bottom = 15.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start - ) { - Text(context.getString(R.string.settings_newsnotify_newsfilter_disabledwarning_description)) - } - }, - opacity = opacity - ) - } else { - SimpleCardItem( - padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), - title = context.getString(R.string.settings_newsnotify_newsfilter_list_title), - opacity = opacity, - content = { - Column( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(horizontal = 15.dp) - .padding(bottom = 15.dp), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.Start - ) { - if (subjectFilterList.size == 0) { - Text(context.getString(R.string.settings_newsnotify_newsfilter_list_nofilters)) - } - subjectFilterList.forEach { code -> - OptionItem( - modifier = Modifier.padding(vertical = 3.dp), - modifierInside = Modifier, - title = "${code.subjectName} [${code.studentYearId}.Nh${code.classId}]", - onClick = { }, - trailingIcon = { - IconButton( - onClick = { - if (fetchNewsInBackgroundDuration > 0) { - onSubjectFilterDelete?.let { it(code) } - } - }, - content = { - Icon(Icons.Default.Delete, context.getString(R.string.action_delete)) - } - ) - } - ) - } - } - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - title = context.getString(R.string.settings_newsnotify_newsfilter_add), - leadingIcon = { Icon(Icons.Default.Add, context.getString(R.string.settings_newsnotify_newsfilter_add)) }, - isEnabled = isNewsSubjectEnabled == 2, - onClick = { - // Add a subject news filter - onSubjectFilterAdd?.let { it() } - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - title = context.getString(R.string.settings_newsnotify_newsfilter_deleteall), - leadingIcon = { Icon(Icons.Default.Delete, context.getString(R.string.settings_newsnotify_newsfilter_deleteall)) }, - isEnabled = isNewsSubjectEnabled == 2, - onClick = { - // Clear all subject news filter list - onSubjectFilterClear?.let { it() } - } - ) - } - } - } -} - @Preview(showBackground = true) @Composable private fun MainViewPreview() { - MainView( - context = LocalContext.current, - fetchNewsInBackgroundDuration = 30, - onFetchNewsStateChanged = { }, - isNewsGlobalEnabled = true, - subjectFilterList = arrayListOf( - SubjectCode("19", "12", "Nhập môn ngành"), - SubjectCode("19", "12", "PBL3") - ), - isNewsSubjectEnabled = 2 - ) +// // TODO: Fix preview for Activity_Settings_NewsNotificationSettings +// Activity_Settings_NewsNotificationSettings( +// context = LocalContext.current, +// fetchNewsInBackgroundDuration = 30, +// onFetchNewsStateChanged = { }, +// isNewsGlobalEnabled = true, +// subjectFilterList = arrayListOf( +// SubjectCode("19", "12", "Nhập môn ngành"), +// SubjectCode("19", "12", "PBL3") +// ), +// isNewsSubjectEnabled = 2 +// ) } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt index 774f839..a37805f 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.settings -import android.app.Activity.RESULT_OK import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -17,47 +16,55 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.SettingsActivity +import io.zoemeow.dutschedule.activity.BaseActivity import io.zoemeow.dutschedule.ui.component.base.SwitchWithTextInSurface @OptIn(ExperimentalMaterial3Api::class) @Composable -fun SettingsActivity.ParseNewsSubjectNotification( +fun Activity_Settings_ParseNewsSubjectNotification( context: Context, snackBarHostState: SnackbarHostState, containerColor: Color, - contentColor: Color + contentColor: Color, + activity: BaseActivity, + onBack: () -> Unit ) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = containerColor, contentColor = contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.settings_parsenewssubject_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { IconButton( onClick = { - setResult(RESULT_OK) - finish() + onBack() }, content = { Icon( @@ -67,7 +74,8 @@ fun SettingsActivity.ParseNewsSubjectNotification( ) } ) - } + }, + scrollBehavior = scrollBehavior ) }, content = { @@ -83,7 +91,7 @@ fun SettingsActivity.ParseNewsSubjectNotification( .padding(bottom = 5.dp), shape = RoundedCornerShape(30.dp), color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = getControlBackgroundAlpha() + alpha = activity.getBackgroundAlpha() ), content = { Column( @@ -91,7 +99,7 @@ fun SettingsActivity.ParseNewsSubjectNotification( .padding(20.dp), content = { Text( - when (getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { + when (activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { true -> context.getString(R.string.settings_parsenewssubject_preview_titleenabled) false -> context.getString(R.string.settings_parsenewssubject_preview_titledisabled) }, @@ -99,7 +107,7 @@ fun SettingsActivity.ParseNewsSubjectNotification( modifier = Modifier.padding(bottom = 5.dp) ) Text( - when (getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { + when (activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { true -> context.getString(R.string.settings_parsenewssubject_preview_descenabled) false -> context.getString(R.string.settings_parsenewssubject_preview_descdisabled) } @@ -111,12 +119,12 @@ fun SettingsActivity.ParseNewsSubjectNotification( SwitchWithTextInSurface( text = context.getString(R.string.settings_parsenewssubject_choice_enable), enabled = true, - checked = getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, + checked = activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, onCheckedChange = { - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - newsBackgroundParseNewsSubject = !getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject + activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( + newsBackgroundParseNewsSubject = !activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject ) - getMainViewModel().saveSettings() + activity.getMainViewModel().saveSettings() } ) Column( From 9b8b73b9d20319921fc5deea45fea50d835f050f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:06:57 +0700 Subject: [PATCH 02/12] 2.0-draft19 (1526) - Update Project - Add "Overview" tab in Main view - Tab view. - Optimized codes. --- app/build.gradle | 2 +- .../dutschedule/activity/AccountActivity.kt | 112 +++- .../dutschedule/activity/BaseActivity.kt | 138 ++--- .../dutschedule/activity/HelpActivity.kt | 40 +- .../dutschedule/activity/MainActivity.kt | 11 +- .../dutschedule/activity/NewsActivity.kt | 56 +- .../activity/PermissionsActivity.kt | 34 +- .../dutschedule/activity/SettingsActivity.kt | 60 +- .../dutschedule/model/AppearanceState.kt | 12 + .../zoemeow/dutschedule/model/NavBarItem.kt | 9 +- .../ui/component/base/SimpleCardItem.kt | 5 +- .../ui/component/main/SummaryItem.kt | 2 - .../ui/view/account/AccountInformation.kt | 37 +- .../dutschedule/ui/view/account/MainView.kt | 64 +- .../dutschedule/ui/view/account/SubjectFee.kt | 40 +- .../ui/view/account/SubjectInformation.kt | 55 +- .../ui/view/account/TrainingResult.kt | 42 +- .../ui/view/account/TrainingSubjectResult.kt | 40 +- .../ui/view/main/MainViewDashboard.kt | 565 ++++++------------ .../ui/view/main/MainViewDashboardView.kt | 277 +++++++++ .../{MainViewTabbed.kt => MainViewTabView.kt} | 77 ++- .../ui/view/main/NotificationScaffold.kt | 19 +- .../dutschedule/ui/view/news/MainView.kt | 40 +- .../dutschedule/ui/view/news/NewsDetail.kt | 48 +- .../dutschedule/ui/view/news/NewsSearch.kt | 21 +- .../ui/view/settings/AboutApplication.kt | 14 +- .../ui/view/settings/ExperimentSettings.kt | 89 +-- .../ui/view/settings/LanguageSettings.kt | 8 +- .../dutschedule/ui/view/settings/MainView.kt | 15 +- .../view/settings/NewsNotificationSettings.kt | 19 +- .../settings/ParseNewsSubjectNotification.kt | 25 +- app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 33 files changed, 1046 insertions(+), 932 deletions(-) create mode 100644 app/src/main/java/io/zoemeow/dutschedule/model/AppearanceState.kt create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt rename app/src/main/java/io/zoemeow/dutschedule/ui/view/main/{MainViewTabbed.kt => MainViewTabView.kt} (79%) diff --git a/app/build.gradle b/app/build.gradle index 440cbab..23ac586 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 34 - versionCode 1493 + versionCode 1526 versionName "2.0-draft19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt index fdf8dcd..d012722 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt @@ -3,14 +3,14 @@ package io.zoemeow.dutschedule.activity import android.content.Context import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import dagger.hilt.android.AndroidEntryPoint -import io.zoemeow.dutschedule.ui.view.account.AccountInformation -import io.zoemeow.dutschedule.ui.view.account.MainView -import io.zoemeow.dutschedule.ui.view.account.SubjectFee -import io.zoemeow.dutschedule.ui.view.account.SubjectInformation -import io.zoemeow.dutschedule.ui.view.account.TrainingResult -import io.zoemeow.dutschedule.ui.view.account.TrainingSubjectResult +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.ui.view.account.Activity_Account +import io.zoemeow.dutschedule.ui.view.account.Activity_Account_AccountInformation +import io.zoemeow.dutschedule.ui.view.account.Activity_Account_SubjectInformation +import io.zoemeow.dutschedule.ui.view.account.Activity_Account_SubjectFee +import io.zoemeow.dutschedule.ui.view.account.Activity_Account_TrainingResult +import io.zoemeow.dutschedule.ui.view.account.Activity_Account_TrainingSubjectResult @AndroidEntryPoint class AccountActivity: BaseActivity() { @@ -31,56 +31,114 @@ class AccountActivity: BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { when (intent.action) { INTENT_SUBJECTINFORMATION -> { - SubjectInformation( + Activity_Account_SubjectInformation( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_SUBJECTFEE -> { - SubjectFee( + Activity_Account_SubjectFee( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_ACCOUNTINFORMATION -> { - AccountInformation( + Activity_Account_AccountInformation( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_ACCOUNTTRAININGSTATUS -> { - TrainingResult( + Activity_Account_TrainingResult( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_ACCOUNTSUBJECTRESULT -> { - TrainingSubjectResult( + Activity_Account_TrainingSubjectResult( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } else -> { - MainView( + Activity_Account( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { text, clearPrevious, actionText, action -> + showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt index 82fd117..f7ead4c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt @@ -1,17 +1,13 @@ package io.zoemeow.dutschedule.activity import android.content.Context -import android.content.Intent import android.graphics.Bitmap -import android.net.Uri import android.os.Bundle import android.os.StrictMode import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.browser.customtabs.CustomTabColorSchemeParams -import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.Image import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxSize @@ -24,17 +20,14 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.platform.SoftwareKeyboardController import androidx.core.view.WindowCompat import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.model.settings.ThemeMode import io.zoemeow.dutschedule.ui.theme.DutScheduleTheme @@ -46,17 +39,15 @@ import kotlinx.coroutines.launch abstract class BaseActivity: ComponentActivity() { companion object { - private lateinit var mainViewModel: MainViewModel + private var mainViewModel: MainViewModel? = null private fun isMainViewModelInitialized(): Boolean { - return ::mainViewModel.isInitialized + return mainViewModel != null } } private lateinit var snackBarHostState: SnackbarHostState private lateinit var snackBarScope: CoroutineScope private val loadScriptAtStartup = mutableStateOf(true) - private var focusManager: FocusManager? = null - private var keyboardController: SoftwareKeyboardController? = null override fun onCreate(savedInstanceState: Bundle?) { // A surface container using the 'background' color from the theme @@ -73,33 +64,29 @@ abstract class BaseActivity: ComponentActivity() { ) ) - permitAllPolicy() + permitAllNetworkPolicy() setContent { // Initialize MainViewModel if (!isMainViewModelInitialized()) { mainViewModel = viewModel() } - // SnackBar state + // Initialize SnackBar state snackBarHostState = remember { SnackbarHostState() } snackBarScope = rememberCoroutineScope() - // Initialize focus manager & software keyboard controller - focusManager = LocalFocusManager.current - keyboardController = LocalSoftwareKeyboardController.current - DutScheduleTheme( - darkTheme = when (mainViewModel.appSettings.value.themeMode) { + darkTheme = when (getMainViewModel().appSettings.value.themeMode) { ThemeMode.DarkMode -> true ThemeMode.LightMode -> false ThemeMode.FollowDeviceTheme -> isSystemInDarkTheme() }, - dynamicColor = mainViewModel.appSettings.value.dynamicColor, + dynamicColor = getMainViewModel().appSettings.value.dynamicColor, translucentStatusBar = getMainViewModel().appSettings.value.backgroundImage != BackgroundImageOption.None, content = { val context = LocalContext.current - val draw: Bitmap? = when (mainViewModel.appSettings.value.backgroundImage) { + val draw: Bitmap? = when (getMainViewModel().appSettings.value.backgroundImage) { BackgroundImageOption.None -> null BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) @@ -113,22 +100,48 @@ abstract class BaseActivity: ComponentActivity() { ) } + @Composable + fun isAppInDarkMode(): Boolean { + return when (getMainViewModel().appSettings.value.themeMode) { + ThemeMode.LightMode -> false + ThemeMode.DarkMode -> true + ThemeMode.FollowDeviceTheme -> isSystemInDarkTheme() + } + } + OnMainView( + context = context, snackBarHostState = snackBarHostState, - containerColor = when (mainViewModel.appSettings.value.backgroundImage) { - BackgroundImageOption.None -> when (mainViewModel.appSettings.value.blackBackground) { - true -> if (isAppInDarkMode()) Color.Black else MaterialTheme.colorScheme.background - false -> MaterialTheme.colorScheme.background + appearanceState = AppearanceState( + containerColor = when (getMainViewModel().appSettings.value.backgroundImage) { + BackgroundImageOption.None -> when (getMainViewModel().appSettings.value.blackBackground) { + true -> if (isAppInDarkMode()) Color.Black else MaterialTheme.colorScheme.background + false -> MaterialTheme.colorScheme.background + } + BackgroundImageOption.YourCurrentWallpaper -> MaterialTheme.colorScheme.background.copy( + alpha = getMainViewModel().appSettings.value.backgroundImageOpacity + ) + BackgroundImageOption.PickFileFromMedia -> MaterialTheme.colorScheme.background.copy( + alpha = getMainViewModel().appSettings.value.backgroundImageOpacity + ) + }, + contentColor = if (isAppInDarkMode()) Color.White else Color.Black, + currentAppModeState = when (getMainViewModel().appSettings.value.themeMode) { + ThemeMode.FollowDeviceTheme -> when (isSystemInDarkTheme()) { + true -> ThemeMode.DarkMode + false -> ThemeMode.LightMode + } + else -> getMainViewModel().appSettings.value.themeMode + }, + backgroundOpacity = when (getMainViewModel().appSettings.value.backgroundImage != BackgroundImageOption.None) { + true -> getMainViewModel().appSettings.value.backgroundImageOpacity + false -> 1f + }, + componentOpacity = when (getMainViewModel().appSettings.value.backgroundImage != BackgroundImageOption.None) { + true -> getMainViewModel().appSettings.value.componentOpacity + false -> 1f } - BackgroundImageOption.YourCurrentWallpaper -> MaterialTheme.colorScheme.background.copy( - alpha = getMainViewModel().appSettings.value.backgroundImageOpacity - ) - BackgroundImageOption.PickFileFromMedia -> MaterialTheme.colorScheme.background.copy( - alpha = getMainViewModel().appSettings.value.backgroundImageOpacity - ) - }, - contentColor = if (isAppInDarkMode()) Color.White else Color.Black, - context = context + ) ) }, ) @@ -141,22 +154,14 @@ abstract class BaseActivity: ComponentActivity() { } } - @Composable - fun isAppInDarkMode( - themeMode: ThemeMode = mainViewModel.appSettings.value.themeMode - ): Boolean { - return when (themeMode) { - ThemeMode.LightMode -> false - ThemeMode.DarkMode -> true - ThemeMode.FollowDeviceTheme -> isSystemInDarkTheme() + fun getBackgroundAlpha(): Float { + return when (getMainViewModel().appSettings.value.backgroundImage != BackgroundImageOption.None) { + true -> getMainViewModel().appSettings.value.backgroundImageOpacity + false -> 1f + // true -> return mainViewModel.appSettings.value. } } - fun clearAllFocusAndHideKeyboard() { - keyboardController?.hide() - focusManager?.clearFocus(force = true) - } - @Composable abstract fun OnPreloadOnce() @@ -164,8 +169,7 @@ abstract class BaseActivity: ComponentActivity() { abstract fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) fun getMainViewModel(): MainViewModel { @@ -173,15 +177,7 @@ abstract class BaseActivity: ComponentActivity() { // Initialize MainViewModel if this isn't initialized before. mainViewModel = ViewModelProvider(this)[MainViewModel::class.java] } - return mainViewModel - } - - fun getBackgroundAlpha(): Float { - return when (mainViewModel.appSettings.value.backgroundImage != BackgroundImageOption.None) { - true -> mainViewModel.appSettings.value.backgroundImageOpacity - false -> 1f - // true -> return mainViewModel.appSettings.value. - } + return mainViewModel!! } fun showSnackBar( @@ -221,30 +217,6 @@ abstract class BaseActivity: ComponentActivity() { } } - fun openLink( - url: String, - context: Context, - customTab: Boolean = true - ) { - when (customTab) { - false -> { - context.startActivity( - Intent(Intent.ACTION_VIEW, Uri.parse(url)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - ) - } - - true -> { - val builder = CustomTabsIntent.Builder() - val defaultColors = CustomTabColorSchemeParams.Builder().build() - builder.setDefaultColorSchemeParams(defaultColors) - - val customTabsIntent = builder.build() - customTabsIntent.launchUrl(context, Uri.parse(url)) - } - } - } - /** * This will bypass network on main thread exception. * Use this at your own risk. @@ -252,7 +224,7 @@ abstract class BaseActivity: ComponentActivity() { * * Source: https://blog.cpming.top/p/android-os-networkonmainthreadexception */ - private fun permitAllPolicy() { + private fun permitAllNetworkPolicy() { val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() StrictMode.setThreadPolicy(policy) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt index 8963d40..3bf9317 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt @@ -35,11 +35,14 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.HelpLinkInfo import io.zoemeow.dutschedule.ui.component.helpandexternallink.HelpLinkClickable +import io.zoemeow.dutschedule.utils.openLink @AndroidEntryPoint class HelpActivity : BaseActivity() { @@ -52,16 +55,14 @@ class HelpActivity : BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { when (intent.action) { "view_externallink" -> { ExternalLinkView( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState ) } @@ -77,17 +78,17 @@ class HelpActivity : BaseActivity() { private fun ExternalLinkView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current val searchText = remember { mutableStateOf("") } Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { Text(text = "External links - DUT School") }, @@ -115,15 +116,19 @@ class HelpActivity : BaseActivity() { .padding(it) .fillMaxSize() .padding(horizontal = 20.dp) - .clickable { clearAllFocusAndHideKeyboard() }, + .clickable { + focusManager.clearFocus(force = true) + }, content = { Surface( modifier = Modifier .fillMaxWidth() .padding(bottom = 7.dp) - .clickable { clearAllFocusAndHideKeyboard() }, + .clickable { + focusManager.clearFocus(force = true) + }, color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = getBackgroundAlpha() + alpha = appearanceState.componentOpacity ), shape = RoundedCornerShape(7.dp), content = { @@ -171,7 +176,7 @@ class HelpActivity : BaseActivity() { keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions( onDone = { - clearAllFocusAndHideKeyboard() + focusManager.clearFocus(force = true) } ) ) @@ -179,7 +184,9 @@ class HelpActivity : BaseActivity() { modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()) - .clickable { clearAllFocusAndHideKeyboard() }, + .clickable { + focusManager.clearFocus(force = true) + }, content = { HelpLinkInfo.getAllExternalLink().filter { item -> searchText.value.isEmpty() || @@ -190,13 +197,12 @@ class HelpActivity : BaseActivity() { HelpLinkClickable( item = item, modifier = Modifier.fillMaxWidth().padding(bottom = 7.dp), - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, linkClicked = { - clearAllFocusAndHideKeyboard() + focusManager.clearFocus(force = true) try { - openLink( + context.openLink( url = item.url, - context = context, customTab = getMainViewModel().appSettings.value.openLinkInsideApp ) } catch (ex: Exception) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt index 9037b98..c096749 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt @@ -6,8 +6,8 @@ import android.util.Log import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.service.BaseService import io.zoemeow.dutschedule.service.NewsBackgroundUpdateService @@ -61,15 +61,13 @@ class MainActivity : BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { if (getMainViewModel().appSettings.value.mainScreenDashboardView) { MainViewDashboard( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, newsClicked = { context.startActivity(Intent(context, NewsActivity::class.java)) }, @@ -89,8 +87,7 @@ class MainActivity : BaseActivity() { MainViewTabbed( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState ) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/NewsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/NewsActivity.kt index 5168f4d..e37d50e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/NewsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/NewsActivity.kt @@ -4,11 +4,12 @@ import android.content.Context import android.content.Intent import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import dagger.hilt.android.AndroidEntryPoint -import io.zoemeow.dutschedule.ui.view.news.MainView -import io.zoemeow.dutschedule.ui.view.news.NewsDetail -import io.zoemeow.dutschedule.ui.view.news.NewsSearch +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.ui.view.news.Activity_News_NewsDetail +import io.zoemeow.dutschedule.ui.view.news.Activity_News +import io.zoemeow.dutschedule.ui.view.news.Activity_News_NewsSearch +import io.zoemeow.dutschedule.utils.openLink @AndroidEntryPoint class NewsActivity : BaseActivity() { @@ -28,38 +29,63 @@ class NewsActivity : BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { when (intent.action) { INTENT_SEARCHACTIVITY -> { - NewsSearch( + Activity_News_NewsSearch( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } INTENT_NEWSDETAILACTIVITY -> { - NewsDetail( + Activity_News_NewsDetail( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor + appearanceState = appearanceState, + newsType = intent.getStringExtra("type"), + newsData = intent.getStringExtra("data"), + onLinkClicked = { link -> + context.openLink( + url = link, + customTab = getMainViewModel().appSettings.value.openLinkInsideApp + ) + }, + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } else -> { - MainView( + Activity_News( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), searchRequested = { val intent = Intent(context, NewsActivity::class.java) intent.action = INTENT_SEARCHACTIVITY context.startActivity(intent) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt index bb70004..4f3dbc4 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt @@ -40,6 +40,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.ui.component.permissionrequest.PermissionInformation class PermissionsActivity : BaseActivity() { @@ -59,16 +61,14 @@ class PermissionsActivity : BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { MainView( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - navIconClicked = { - setResult(RESULT_OK) + appearanceState = appearanceState, + onBack = { + setResult(RESULT_CANCELED) finish() }, fabClicked = { @@ -84,15 +84,13 @@ class PermissionsActivity : BaseActivity() { @OptIn(ExperimentalMaterial3Api::class) @Composable - // TODO: Using of context. private fun MainView( - @Suppress("UNUSED_PARAMETER") context: Context, + context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - navIconClicked: (() -> Unit)? = null, + appearanceState: AppearanceState, fabClicked: (() -> Unit)? = null, - permissionRequest: ((String) -> Unit)? = null + permissionRequest: ((String) -> Unit)? = null, + onBack: (() -> Unit)? = null ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -100,8 +98,8 @@ class PermissionsActivity : BaseActivity() { modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(text = "Permissions request") }, @@ -109,12 +107,12 @@ class PermissionsActivity : BaseActivity() { navigationIcon = { IconButton( onClick = { - navIconClicked?.let { it() } + onBack?.let { it() } }, content = { Icon( Icons.AutoMirrored.Filled.ArrowBack, - "", + context.getString(R.string.action_back), modifier = Modifier.size(25.dp) ) } @@ -126,7 +124,7 @@ class PermissionsActivity : BaseActivity() { bottomBar = { BottomAppBar( containerColor = BottomAppBarDefaults.containerColor.copy( - alpha = getBackgroundAlpha() + alpha = appearanceState.backgroundOpacity ), floatingActionButton = { ExtendedFloatingActionButton( @@ -164,7 +162,7 @@ class PermissionsActivity : BaseActivity() { isRequired = false, isGranted = item.isGranted, padding = PaddingValues(bottom = 10.dp), - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, clicked = { permissionRequest?.let { if (item.isGranted) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt index 363d408..037ccc4 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt @@ -7,11 +7,12 @@ import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import dagger.hilt.android.AndroidEntryPoint import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings +import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_AboutApplication import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_AppLanguageSettings import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ExperimentSettings import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_NewsNotificationSettings @@ -25,6 +26,7 @@ class SettingsActivity : BaseActivity() { const val INTENT_EXPERIMENTSETTINGS = "settings_experimentsettings" const val INTENT_LANGUAGESETTINGS = "settings_languagesettings" const val INTENT_NEWSNOTIFICATIONSETTINGS = "settings_newsnotificaitonsettings" + const val INTENT_ABOUTACTIVITY = "settings_about" } @Composable @@ -62,17 +64,21 @@ class SettingsActivity : BaseActivity() { override fun OnMainView( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState ) { when (intent.action) { INTENT_PARSENEWSSUBJECTNOTIFICATION -> { Activity_Settings_ParseNewsSubjectNotification( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - activity = this, + appearanceState = appearanceState, + isEnabled = getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, + onChange = { + getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( + newsBackgroundParseNewsSubject = !getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject + ) + getMainViewModel().saveSettings() + }, onBack = { setResult(RESULT_CANCELED) finish() @@ -84,9 +90,16 @@ class SettingsActivity : BaseActivity() { Activity_Settings_ExperimentSettings( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - activity = this, + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, onBack = { setResult(RESULT_CANCELED) finish() @@ -98,8 +111,7 @@ class SettingsActivity : BaseActivity() { Activity_Settings_AppLanguageSettings( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, onBack = { setResult(RESULT_CANCELED) finish() @@ -111,8 +123,7 @@ class SettingsActivity : BaseActivity() { Activity_Settings_NewsNotificationSettings( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, onBack = { setResult(RESULT_CANCELED) finish() @@ -194,10 +205,12 @@ class SettingsActivity : BaseActivity() { ) getMainViewModel().appSettings.value = dataTemp getMainViewModel().saveSettings(saveSettingsOnly = true) + @Suppress("KotlinConstantConditions") showSnackBar( text = when (code) { -1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_disabled) 0 -> context.getString(R.string.settings_newsnotify_newssubject_notify_all) + // TODO: Implement this branch "Match your schedule" to avoid issue 1 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchsubsch) 2 -> context.getString(R.string.settings_newsnotify_newssubject_notify_matchfilter) // TODO: No code valid @@ -252,8 +265,19 @@ class SettingsActivity : BaseActivity() { clearPrevious = true ) } catch (_: Exception) { } - }, - opacity = getBackgroundAlpha() + } + ) + } + + INTENT_ABOUTACTIVITY -> { + Activity_Settings_AboutApplication( + context = context, + snackBarHostState = snackBarHostState, + appearanceState = appearanceState, + onBack = { + setResult(RESULT_CANCELED) + finish() + } ) } @@ -261,11 +285,9 @@ class SettingsActivity : BaseActivity() { Activity_Settings( context = context, snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getBackgroundAlpha(), + appearanceState = appearanceState, mainViewModel = getMainViewModel(), - onShowSnackBar = { text, clearPrevious, actionText, action -> + onMessageReceived = { text, clearPrevious, actionText, action -> showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) }, onBack = { diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/AppearanceState.kt b/app/src/main/java/io/zoemeow/dutschedule/model/AppearanceState.kt new file mode 100644 index 0000000..0d6072d --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/model/AppearanceState.kt @@ -0,0 +1,12 @@ +package io.zoemeow.dutschedule.model + +import androidx.compose.ui.graphics.Color +import io.zoemeow.dutschedule.model.settings.ThemeMode + +data class AppearanceState( + val containerColor: Color, + val contentColor: Color, + val currentAppModeState: ThemeMode, + val backgroundOpacity: Float = 1f, + val componentOpacity: Float = 1f +) \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt b/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt index a691054..6b796be 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt @@ -2,6 +2,7 @@ package io.zoemeow.dutschedule.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Settings import androidx.compose.ui.graphics.vector.ImageVector @@ -14,6 +15,12 @@ data class NavBarItem( val route: String ) { companion object { + val dashboard = NavBarItem( + titleResId = R.string.main_dashboard_title, + icon = Icons.Default.Home, + route = "dashboard" + ) + val news = NavBarItem( titleResId = R.string.news_title, resourceIconId = R.drawable.ic_baseline_newspaper_24, @@ -39,7 +46,7 @@ data class NavBarItem( ) fun getAll(): List { - return listOf(news, account, notification, settings) + return listOf(dashboard, news, account, notification, settings) } } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt index 67b8888..6c41f8e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt @@ -39,8 +39,9 @@ fun SimpleCardItem( .padding(padding) .clip(RoundedCornerShape(7.dp)) .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = opacity)) - .apply { - if (clicked != null) this.clickable { clicked() } + .run { + if (clicked != null) return@run this.clickable { clicked() } + else return@run this } ) { Row( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt index 9cd52f8..5ce4089 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt @@ -1,7 +1,6 @@ package io.zoemeow.dutschedule.ui.component.main 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.PaddingValues @@ -42,7 +41,6 @@ fun SummaryItem( .wrapContentHeight() .padding(padding) .clip(RoundedCornerShape(7.dp)) - .clickable { clicked() } .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0f)), ) { CircularProgressIndicator() diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt index 92a9ab8..eb90686 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_OK import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -47,26 +46,29 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.base.OutlinedTextBox +import io.zoemeow.dutschedule.viewmodel.MainViewModel import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountActivity.AccountInformation( +fun Activity_Account_AccountInformation( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onBack: () -> Unit ) { val clipboardManager: ClipboardManager = LocalClipboardManager.current val scope = rememberCoroutineScope() Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -77,8 +79,7 @@ fun AccountActivity.AccountInformation( navigationIcon = { IconButton( onClick = { - setResult(RESULT_OK) - finish() + onBack() }, content = { Icon( @@ -90,7 +91,7 @@ fun AccountActivity.AccountInformation( ) } ) - if (getMainViewModel().accountSession.accountInformation.processState.value == ProcessState.Running) { + if (mainViewModel.accountSession.accountInformation.processState.value == ProcessState.Running) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } @@ -100,10 +101,10 @@ fun AccountActivity.AccountInformation( val pageInfoTooltipState = rememberTooltipState(isPersistent = true) BottomAppBar( floatingActionButton = { - if (getMainViewModel().accountSession.accountInformation.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.accountInformation.processState.value != ProcessState.Running) { FloatingActionButton( onClick = { - getMainViewModel().accountSession.fetchAccountInformation(force = true) + mainViewModel.accountSession.fetchAccountInformation(force = true) }, content = { Icon( @@ -160,18 +161,12 @@ fun AccountActivity.AccountInformation( fun copyToClipboard(s: String? = null) { if (!s.isNullOrEmpty()) { clipboardManager.setText(AnnotatedString(s)) - showSnackBar( - context.getString(R.string.account_accinfo_snackbar_copied), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.account_accinfo_snackbar_copied), true, null, null) } else { - showSnackBar( - context.getString(R.string.account_accinfo_snackbar_nocopy), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.account_accinfo_snackbar_nocopy), true, null, null) } } - getMainViewModel().accountSession.accountInformation.data.value?.let { data -> + mainViewModel.accountSession.accountInformation.data.value?.let { data -> Column( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt index da376cf..e3206b2 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_CANCELED import android.content.Context import android.content.Intent import androidx.activity.compose.BackHandler @@ -33,6 +32,7 @@ import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.account.AccountAuth import io.zoemeow.dutschedule.ui.component.account.AccountInfoBanner @@ -45,40 +45,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -@Composable -fun AccountActivity.MainView( - context: Context, - snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color -) { - AccountMainView( - context = context, - snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getBackgroundAlpha(), - mainViewModel = getMainViewModel(), - onShowSnackBar = { text, clearPrevious, actionText, action -> - showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) - }, - onBack = { - setResult(RESULT_CANCELED) - finish() - } - ) -} - @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountMainView( +fun Activity_Account( context: Context, snackBarHostState: SnackbarHostState? = null, - containerColor: Color, - contentColor: Color, - componentBackgroundAlpha: Float = 1f, + appearanceState: AppearanceState, mainViewModel: MainViewModel, - onShowSnackBar: ((String, Boolean, String?, (() -> Unit)?) -> Unit)? = null, + onMessageReceived: ((String, Boolean, String?, (() -> Unit)?) -> Unit)? = null, onBack: (() -> Unit)? = null ) { val loginDialogVisible = remember { mutableStateOf(false) } @@ -88,8 +62,8 @@ fun AccountMainView( Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { Text(context.getString(R.string.account_title)) }, @@ -126,7 +100,7 @@ fun AccountMainView( mainViewModel.accountSession.accountInformation.let { accInfo -> AccountInfoBanner( context = context, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, padding = PaddingValues(10.dp), isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || accInfo.processState.value == ProcessState.Running, isFailed = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Failed, @@ -147,7 +121,7 @@ fun AccountMainView( isEnabled = mainViewModel.accountSession.accountSession.processState.value != ProcessState.Running, content = { Text(context.getString(R.string.account_dashboard_button_subjectinfo)) }, horizontalArrangement = Arrangement.Start, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, clicked = { val intent = Intent(context, AccountActivity::class.java) intent.action = AccountActivity.INTENT_SUBJECTINFORMATION @@ -162,7 +136,7 @@ fun AccountMainView( isEnabled = mainViewModel.accountSession.accountSession.processState.value != ProcessState.Running, content = { Text(context.getString(R.string.account_dashboard_button_subjectfee)) }, horizontalArrangement = Arrangement.Start, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, clicked = { val intent = Intent(context, AccountActivity::class.java) intent.action = AccountActivity.INTENT_SUBJECTFEE @@ -177,7 +151,7 @@ fun AccountMainView( isEnabled = mainViewModel.accountSession.accountSession.processState.value != ProcessState.Running, content = { Text(context.getString(R.string.account_dashboard_button_accountinfo)) }, horizontalArrangement = Arrangement.Start, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, clicked = { val intent = Intent(context, AccountActivity::class.java) intent.action = AccountActivity.INTENT_ACCOUNTINFORMATION @@ -192,7 +166,7 @@ fun AccountMainView( isEnabled = mainViewModel.accountSession.accountSession.processState.value != ProcessState.Running, content = { Text(context.getString(R.string.account_dashboard_button_accounttrainstats)) }, horizontalArrangement = Arrangement.Start, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, clicked = { val intent = Intent(context, AccountActivity::class.java) intent.action = AccountActivity.INTENT_ACCOUNTTRAININGSTATUS @@ -206,7 +180,7 @@ fun AccountMainView( modifierInside = Modifier.padding(vertical = 7.dp), content = { Text(context.getString(R.string.account_dashboard_button_logout)) }, horizontalArrangement = Arrangement.Start, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, clicked = { logoutDialogVisible.value = true } @@ -224,7 +198,7 @@ fun AccountMainView( isControlEnabled = state != ProcessState.Running, isLoggedInBefore = state == ProcessState.Failed, clearOnInvisible = true, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, onForgotPass = { context.openLink( url = GlobalVariables.LINK_FORGOT_PASSWORD, @@ -239,7 +213,7 @@ fun AccountMainView( run { CoroutineScope(Dispatchers.IO).launch { loginDialogEnabled.value = false - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_login_loggingin), true, null, null ) } @@ -253,14 +227,14 @@ fun AccountMainView( loginDialogEnabled.value = true loginDialogVisible.value = false mainViewModel.accountSession.reLogin() - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_login_successful), true, null, null ) } } false -> { loginDialogEnabled.value = true - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_login_failed), true, null, null ) } @@ -283,14 +257,14 @@ fun AccountMainView( loginDialogEnabled.value = true loginDialogVisible.value = false mainViewModel.accountSession.reLogin() - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_login_successful), true, null, null ) } } false -> { loginDialogEnabled.value = true - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_login_failed), true, null, null ) } @@ -315,7 +289,7 @@ fun AccountMainView( logoutDialogVisible.value = false mainViewModel.accountSession.logout( onCompleted = { - onShowSnackBar?.let { it( + onMessageReceived?.let { it( context.getString(R.string.account_logout_loggedout), true, null, null ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt index e6f3fec..4cd70f8 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectFee.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_OK import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -34,23 +33,25 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.account.AccountSubjectFeeInformation +import io.zoemeow.dutschedule.viewmodel.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountActivity.SubjectFee( +fun Activity_Account_SubjectFee( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onBack: () -> Unit ) { Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -61,8 +62,7 @@ fun AccountActivity.SubjectFee( navigationIcon = { IconButton( onClick = { - setResult(RESULT_OK) - finish() + onBack() }, content = { Icon( @@ -74,17 +74,17 @@ fun AccountActivity.SubjectFee( ) } ) - if (getMainViewModel().accountSession.subjectFee.processState.value == ProcessState.Running) { + if (mainViewModel.accountSession.subjectFee.processState.value == ProcessState.Running) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } ) }, floatingActionButton = { - if (getMainViewModel().accountSession.subjectFee.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.subjectFee.processState.value != ProcessState.Running) { FloatingActionButton( onClick = { - getMainViewModel().accountSession.fetchSubjectFee(force = true) + mainViewModel.accountSession.fetchSubjectFee(force = true) }, content = { Icon(Icons.Default.Refresh, context.getString(R.string.action_refresh)) @@ -105,10 +105,10 @@ fun AccountActivity.SubjectFee( .padding(vertical = 2.dp), horizontalAlignment = Alignment.CenterHorizontally, content = { - Text(getMainViewModel().appSettings.value.currentSchoolYear.composeToString()) + Text(mainViewModel.appSettings.value.currentSchoolYear.composeToString()) } ) - if (getMainViewModel().accountSession.subjectFee.data.size == 0 && getMainViewModel().accountSession.subjectFee.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.subjectFee.data.size == 0 && mainViewModel.accountSession.subjectFee.processState.value != ProcessState.Running) { Column( modifier = Modifier.fillMaxSize() .padding(horizontal = 15.dp) @@ -132,19 +132,19 @@ fun AccountActivity.SubjectFee( verticalArrangement = Arrangement.Top, content = { item { - if (getMainViewModel().accountSession.subjectFee.data.size > 0) { + if (mainViewModel.accountSession.subjectFee.data.size > 0) { Text(context.getString( R.string.account_subjectfee_summary_main, - getMainViewModel().accountSession.subjectFee.data.sumOf { it.credit }, - getMainViewModel().accountSession.subjectFee.data.sumOf { it.price } + mainViewModel.accountSession.subjectFee.data.sumOf { it.credit }, + mainViewModel.accountSession.subjectFee.data.sumOf { it.price } )) } } - items(getMainViewModel().accountSession.subjectFee.data) { item -> + items(mainViewModel.accountSession.subjectFee.data) { item -> AccountSubjectFeeInformation( modifier = Modifier.padding(bottom = 10.dp), item = item, - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, onClick = { } ) } @@ -159,7 +159,7 @@ fun AccountActivity.SubjectFee( val hasRun = remember { mutableStateOf(false) } run { if (!hasRun.value) { - getMainViewModel().accountSession.fetchSubjectFee() + mainViewModel.accountSession.fetchSubjectFee() hasRun.value = true } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt index 890e205..a95ed23 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_CANCELED import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -36,18 +35,21 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.account.AccountSubjectMoreInformation import io.zoemeow.dutschedule.ui.component.account.SubjectInformation +import io.zoemeow.dutschedule.viewmodel.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountActivity.SubjectInformation( +fun Activity_Account_SubjectInformation( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onBack: () -> Unit ) { val subjectScheduleItem: MutableState = remember { mutableStateOf(null) } val subjectDetailVisible = remember { mutableStateOf(false) } @@ -55,8 +57,8 @@ fun AccountActivity.SubjectInformation( Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -67,8 +69,7 @@ fun AccountActivity.SubjectInformation( navigationIcon = { IconButton( onClick = { - setResult(RESULT_CANCELED) - finish() + onBack() }, content = { Icon( @@ -80,17 +81,17 @@ fun AccountActivity.SubjectInformation( ) } ) - if (getMainViewModel().accountSession.subjectSchedule.processState.value == ProcessState.Running) { + if (mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } ) }, floatingActionButton = { - if (getMainViewModel().accountSession.subjectSchedule.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.subjectSchedule.processState.value != ProcessState.Running) { FloatingActionButton( onClick = { - getMainViewModel().accountSession.fetchSubjectSchedule(force = true) + mainViewModel.accountSession.fetchSubjectSchedule(force = true) }, content = { Icon(Icons.Default.Refresh, context.getString(R.string.action_refresh)) @@ -111,10 +112,10 @@ fun AccountActivity.SubjectInformation( .padding(vertical = 2.dp), horizontalAlignment = Alignment.CenterHorizontally, content = { - Text(getMainViewModel().appSettings.value.currentSchoolYear.composeToString()) + Text(mainViewModel.appSettings.value.currentSchoolYear.composeToString()) } ) - if (getMainViewModel().accountSession.subjectSchedule.data.size == 0 && getMainViewModel().accountSession.subjectSchedule.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.subjectSchedule.data.size == 0 && mainViewModel.accountSession.subjectSchedule.processState.value != ProcessState.Running) { Column( modifier = Modifier.fillMaxSize() .padding(horizontal = 15.dp) @@ -137,11 +138,11 @@ fun AccountActivity.SubjectInformation( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Top, content = { - items(getMainViewModel().accountSession.subjectSchedule.data) { item -> + items(mainViewModel.accountSession.subjectSchedule.data) { item -> SubjectInformation( modifier = Modifier.padding(bottom = 7.dp), item = item, - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, onClick = { subjectScheduleItem.value = item subjectDetailVisible.value = true @@ -163,25 +164,19 @@ fun AccountActivity.SubjectInformation( subjectDetailVisible.value = false }, onAddToFilterRequested = { item -> - if (getMainViewModel().appSettings.value.newsBackgroundFilterList.any { it.isEquals(item) }) { - showSnackBar( - text = context.getString(R.string.account_subjectinfo_filter_alreadyadded), - clearPrevious = true - ) + if (mainViewModel.appSettings.value.newsBackgroundFilterList.any { it.isEquals(item) }) { + onMessageReceived(context.getString(R.string.account_subjectinfo_filter_alreadyadded), true, null, null) } else { - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - newsFilterList = getMainViewModel().appSettings.value.newsBackgroundFilterList.also { + mainViewModel.appSettings.value = mainViewModel.appSettings.value.clone( + newsFilterList = mainViewModel.appSettings.value.newsBackgroundFilterList.also { it.add(item) } ) - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString( + mainViewModel.saveSettings() + onMessageReceived(context.getString( R.string.account_subjectinfo_filter_added, item - ), - clearPrevious = true - ) + ), true, null, null) } } ) @@ -189,7 +184,7 @@ fun AccountActivity.SubjectInformation( val hasRun = remember { mutableStateOf(false) } run { if (!hasRun.value) { - getMainViewModel().accountSession.fetchSubjectSchedule() + mainViewModel.accountSession.fetchSubjectSchedule() hasRun.value = true } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt index 5ffc557..2961f27 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_CANCELED import android.content.Context import android.content.Intent import androidx.compose.foundation.layout.Arrangement @@ -52,26 +51,30 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.base.ButtonBase import io.zoemeow.dutschedule.ui.component.base.CheckboxOption import io.zoemeow.dutschedule.ui.component.base.OutlinedTextBox import io.zoemeow.dutschedule.ui.component.base.SimpleCardItem +import io.zoemeow.dutschedule.viewmodel.MainViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun AccountActivity.TrainingResult( +fun Activity_Account_TrainingResult( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onBack: () -> Unit ) { val clipboardManager: ClipboardManager = LocalClipboardManager.current Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -82,8 +85,7 @@ fun AccountActivity.TrainingResult( navigationIcon = { IconButton( onClick = { - setResult(RESULT_CANCELED) - finish() + onBack() }, content = { Icon( @@ -95,7 +97,7 @@ fun AccountActivity.TrainingResult( ) } ) - if (getMainViewModel().accountSession.accountTrainingStatus.processState.value == ProcessState.Running) { + if (mainViewModel.accountSession.accountTrainingStatus.processState.value == ProcessState.Running) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } @@ -105,10 +107,10 @@ fun AccountActivity.TrainingResult( BottomAppBar( containerColor = Color.Transparent, floatingActionButton = { - if (getMainViewModel().accountSession.accountTrainingStatus.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.accountTrainingStatus.processState.value != ProcessState.Running) { FloatingActionButton( onClick = { - getMainViewModel().accountSession.fetchAccountTrainingStatus(force = true) + mainViewModel.accountSession.fetchAccountTrainingStatus(force = true) }, content = { Icon(Icons.Default.Refresh, context.getString(R.string.action_refresh)) @@ -132,12 +134,12 @@ fun AccountActivity.TrainingResult( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally, content = { - getMainViewModel().accountSession.accountTrainingStatus.data.value?.let { + mainViewModel.accountSession.accountTrainingStatus.data.value?.let { SimpleCardItem( title = context.getString(R.string.account_trainingstatus_trainbox_title), isTitleCentered = true, padding = PaddingValues(start = 10.dp, end = 10.dp, bottom = 7.dp), - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, content = { Column( modifier = Modifier @@ -215,7 +217,7 @@ fun AccountActivity.TrainingResult( title = context.getString(R.string.account_trainingstatus_graduatebox_title), isTitleCentered = true, padding = PaddingValues(horizontal = 10.dp), - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, content = { Column( modifier = Modifier @@ -259,15 +261,9 @@ fun AccountActivity.TrainingResult( fun copyToClipboard(s: String? = null) { if (!s.isNullOrEmpty()) { clipboardManager.setText(AnnotatedString(s)) - showSnackBar( - context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_copied), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_copied), true, null, null) } else { - showSnackBar( - context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_nocopy), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_nocopy), true, null, null) } } OutlinedTextBox( @@ -359,7 +355,7 @@ fun AccountActivity.TrainingResult( val hasRun = remember { mutableStateOf(false) } run { if (!hasRun.value) { - getMainViewModel().accountSession.fetchAccountTrainingStatus() + mainViewModel.accountSession.fetchAccountTrainingStatus() hasRun.value = true } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt index 6be2c2d..212ad7c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.account -import android.app.Activity.RESULT_CANCELED import android.content.Context import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically @@ -55,6 +54,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction @@ -63,22 +63,27 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.dutwrapper.dutwrapper.model.accounts.trainingresult.SubjectResult import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.account.SubjectResult import io.zoemeow.dutschedule.ui.component.base.OutlinedTextBox import io.zoemeow.dutschedule.utils.toNonAccent +import io.zoemeow.dutschedule.viewmodel.MainViewModel import java.util.Locale @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable -fun AccountActivity.TrainingSubjectResult( +fun Activity_Account_TrainingSubjectResult( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + // TODO: onMessageReceived when copy a property + @Suppress("UNUSED_PARAMETER") onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onBack: () -> Unit ) { val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current // Search area (true to display them) val searchEnabled = remember { mutableStateOf(false) } @@ -126,8 +131,8 @@ fun AccountActivity.TrainingSubjectResult( Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -140,8 +145,7 @@ fun AccountActivity.TrainingSubjectResult( navigationIcon = { IconButton( onClick = { - setResult(RESULT_CANCELED) - finish() + onBack() }, content = { Icon( @@ -153,7 +157,7 @@ fun AccountActivity.TrainingSubjectResult( ) } ) - if (getMainViewModel().accountSession.accountTrainingStatus.processState.value == ProcessState.Running) { + if (mainViewModel.accountSession.accountTrainingStatus.processState.value == ProcessState.Running) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } } @@ -182,11 +186,11 @@ fun AccountActivity.TrainingSubjectResult( ) }, floatingActionButton = { - if (getMainViewModel().accountSession.accountTrainingStatus.processState.value != ProcessState.Running) { + if (mainViewModel.accountSession.accountTrainingStatus.processState.value != ProcessState.Running) { FloatingActionButton( onClick = { - clearAllFocusAndHideKeyboard() - getMainViewModel().accountSession.fetchAccountTrainingStatus(force = true) + focusManager.clearFocus(force = true) + mainViewModel.accountSession.fetchAccountTrainingStatus(force = true) }, content = { Icon( @@ -246,7 +250,7 @@ fun AccountActivity.TrainingSubjectResult( } } } - items(getMainViewModel().accountSession.accountTrainingStatus.data.value?.subjectResultList?.filter { p -> run { + items(mainViewModel.accountSession.accountTrainingStatus.data.value?.subjectResultList?.filter { p -> run { // Filter with school year if (schYearOptionText.value.isEmpty()) return@run true if (p.schoolYear == schYearOptionText.value) return@run true @@ -264,7 +268,7 @@ fun AccountActivity.TrainingSubjectResult( selectedSubject.value = subjectItem modalBottomSheetEnabled.value = true }, - opacity = getBackgroundAlpha() + opacity = appearanceState.componentOpacity ) } } @@ -320,7 +324,7 @@ fun AccountActivity.TrainingSubjectResult( schYearShowOption.value = false } ) - (getMainViewModel().accountSession.accountTrainingStatus.data.value?.subjectResultList?.map { it.schoolYear }?.toList()?.distinct()?.reversed() ?: listOf()).forEach { + (mainViewModel.accountSession.accountTrainingStatus.data.value?.subjectResultList?.map { it.schoolYear }?.toList()?.distinct()?.reversed() ?: listOf()).forEach { DropdownMenuItem( modifier = Modifier.background( color = when (schYearOptionText.value == it) { @@ -356,7 +360,7 @@ fun AccountActivity.TrainingSubjectResult( ), keyboardActions = KeyboardActions( onDone = { - clearAllFocusAndHideKeyboard() + focusManager.clearFocus(force = true) } ), trailingIcon = { @@ -411,7 +415,7 @@ fun AccountActivity.TrainingSubjectResult( val hasRun = remember { mutableStateOf(false) } run { if (!hasRun.value) { - getMainViewModel().accountSession.fetchAccountTrainingStatus() + mainViewModel.accountSession.fetchAccountTrainingStatus() hasRun.value = true } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index aa0b451..9c8e280 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -1,383 +1,184 @@ -package io.zoemeow.dutschedule.ui.view.main - -import android.content.Context -import android.content.Intent -import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.outlined.AccountCircle -import androidx.compose.material3.Badge -import androidx.compose.material3.BadgedBox -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.BottomAppBarDefaults -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import io.zoemeow.dutschedule.GlobalVariables -import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.AccountActivity -import io.zoemeow.dutschedule.activity.MainActivity -import io.zoemeow.dutschedule.activity.NewsActivity -import io.zoemeow.dutschedule.model.CustomClock -import io.zoemeow.dutschedule.model.ProcessState -import io.zoemeow.dutschedule.model.settings.BackgroundImageOption -import io.zoemeow.dutschedule.ui.component.main.DateAndTimeSummaryItem -import io.zoemeow.dutschedule.ui.component.main.LessonTodaySummaryItem -import io.zoemeow.dutschedule.ui.component.main.SchoolNewsSummaryItem -import io.zoemeow.dutschedule.ui.component.main.UpdateAvailableSummaryItem -import io.zoemeow.dutschedule.utils.BackgroundImageUtil -import io.zoemeow.dutschedule.utils.CustomDateUtil -import kotlinx.datetime.Clock -import kotlinx.datetime.LocalDateTime -import kotlinx.datetime.LocalTime -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toInstant -import kotlinx.datetime.toLocalDateTime -import kotlin.time.Duration.Companion.days - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun MainActivity.MainViewDashboard( - context: Context, - snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - newsClicked: (() -> Unit)? = null, - accountClicked: (() -> Unit)? = null, - settingsClicked: (() -> Unit)? = null, - externalLinkClicked: (() -> Unit)? = null -) { - val isNotificationOpened = remember { mutableStateOf(false) } - - Scaffold( - modifier = Modifier.fillMaxSize(), - snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, - topBar = { - TopAppBar( - title = { Text(text = context.getString(R.string.app_name)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) - ) - }, - bottomBar = { - BottomAppBar( - containerColor = BottomAppBarDefaults.containerColor.copy( - alpha = getBackgroundAlpha() - ), - actions = { - BadgedBox( - // modifier = Modifier.padding(start = 15.dp, end = 15.dp), - badge = { - // Badge { } - } - ) { - IconButton( - onClick = { newsClicked?.let { it() } } - ) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_newspaper_24), - context.getString(R.string.news_title), - modifier = Modifier.size(27.dp) - ) - } - } - BadgedBox( - // modifier = Modifier.padding(end = 15.dp), - badge = { - // Badge { } - } - ) { - IconButton( - onClick = { settingsClicked?.let { it() } } - ) { - Icon( - Icons.Default.Settings, - context.getString(R.string.settings_title), - modifier = Modifier.size(27.dp) - ) - } - } - BadgedBox( - // modifier = Modifier.padding(end = 15.dp), - badge = { - // Badge { } - } - ) { - IconButton(onClick = { externalLinkClicked?.let { it() } }) { - Icon( - painter = painterResource(id = R.drawable.ic_baseline_web_24), - "External links", - modifier = Modifier.size(27.dp) - ) - } - } - BadgedBox( - // modifier = Modifier.padding(end = 15.dp), - badge = { - if (getMainViewModel().notificationHistory.isNotEmpty()) { - Badge { - Text(getMainViewModel().notificationHistory.size.toString()) - } - } - }, - content = { - IconButton( - onClick = { - // Open notification bottom sheet - // Notification list requested - if (!isNotificationOpened.value) { - isNotificationOpened.value = true - } - } - ) { - Icon( - imageVector = Icons.Default.Notifications, - context.getString(R.string.notification_panel_title), - modifier = Modifier.size(27.dp), - ) - } - } - ) - }, - floatingActionButton = { - ExtendedFloatingActionButton( - text = { - Column( - modifier = Modifier.height(60.dp), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.Center, - content = { - Text( - context.getString(R.string.account_title), - style = MaterialTheme.typography.titleSmall - ) - getMainViewModel().accountSession.accountSession.processState.value.let { - Text( - when (it) { - ProcessState.NotRunYet -> context.getString(R.string.main_account_notloggedin) - ProcessState.Running -> context.getString(R.string.main_account_fetching) - else -> getMainViewModel().accountSession.accountSession.data.value?.accountAuth?.username ?: "unknown" - }, - style = MaterialTheme.typography.bodySmall - ) - } - } - ) - }, - icon = { - BadgedBox( - badge = { - if (getMainViewModel().accountSession.accountSession.processState.value == ProcessState.Failed) { - Badge { Text("!") } - } - }, - content = { - when (getMainViewModel().accountSession.accountSession.processState.value) { - ProcessState.Running -> CircularProgressIndicator( - modifier = Modifier.size(26.dp), - strokeWidth = 3.dp - ) - else -> Icon( - Icons.Outlined.AccountCircle, - context.getString(R.string.account_title), - modifier = Modifier.size(26.dp) - ) - } - } - ) - }, - onClick = { accountClicked?.let { it() } } - ) - } - ) - }, - content = { padding -> - Surface( - modifier = Modifier - .fillMaxSize() - .padding(padding), - color = Color.Transparent, - content = { - Column( - modifier = Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), - content = { - DateAndTimeSummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - isLoading = getMainViewModel().currentSchoolYearWeek.processState.value == ProcessState.Running, - currentSchoolWeek = getMainViewModel().currentSchoolYearWeek.data.value, - opacity = getBackgroundAlpha() - ) - LessonTodaySummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - hasLoggedIn = getMainViewModel().accountSession.accountSession.processState.value == ProcessState.Successful, - isLoading = getMainViewModel().accountSession.accountSession.processState.value == ProcessState.Running || getMainViewModel().accountSession.subjectSchedule.processState.value == ProcessState.Running, - clicked = { - getMainViewModel().accountSession.reLogin( - onCompleted = { - if (it) { - val intent = Intent(context, AccountActivity::class.java) - intent.action = AccountActivity.INTENT_SUBJECTINFORMATION - context.startActivity(intent) - } - } - ) - }, - affectedList = getMainViewModel().accountSession.subjectSchedule.data.filter { subSch -> - subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && - subSch.subjectStudy.scheduleList.any { schItem -> - schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson - } - }.toList(), - opacity = getBackgroundAlpha() - ) - // AffectedLessonsSummaryItem( -// padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), -// hasLoggedIn = getMainViewModel().accountSession.value.processState == ProcessState.Successful, -// isLoading = getMainViewModel().accountSession.value.processState == ProcessState.Running || getMainViewModel().subjectSchedule.processState.value == ProcessState.Running, -// clicked = {}, -// affectedList = arrayListOf("ie1i0921d - i029di12", "ie1i0921d - i029di12","ie1i0921d - i029di12","ie1i0921d - i029di12","ie1i0921d - i029di12"), -// opacity = getControlBackgroundAlpha() -// ) - SchoolNewsSummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - newsToday = run { - val today = LocalDateTime( - Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, - LocalTime(0, 0, 0) - ).toInstant(TimeZone.UTC) - - // https://stackoverflow.com/questions/77368433/how-to-get-current-date-with-reset-time-0000-with-kotlinx-localdatetime - return@run getMainViewModel().newsInstance.newsGlobal.data.filter { it.date == today.toEpochMilliseconds() }.size - }, - newsThisWeek = run { - val today = LocalDateTime( - Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, - LocalTime(0, 0, 0) - ).toInstant(TimeZone.UTC) - val before7Days = today.minus(7.days) - - // https://stackoverflow.com/questions/77368433/how-to-get-current-date-with-reset-time-0000-with-kotlinx-localdatetime - return@run getMainViewModel().newsInstance.newsGlobal.data.filter { it.date <= today.toEpochMilliseconds() && it.date >= before7Days.toEpochMilliseconds() }.size - }, - clicked = { - context.startActivity(Intent(context, NewsActivity::class.java)) - }, - isLoading = getMainViewModel().newsInstance.newsGlobal.processState.value == ProcessState.Running, - opacity = getBackgroundAlpha() - ) - UpdateAvailableSummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - isLoading = false, - updateAvailable = false, - latestVersionString = "", - clicked = { - openLink( - url = GlobalVariables.LINK_REPOSITORY_RELEASE, - context = context, - customTab = false, - ) - }, - opacity = getBackgroundAlpha() - ) - }, - ) - } - ) - } - ) - NotificationScaffold( - context = context, - itemList = getMainViewModel().notificationHistory, - snackBarHostState = snackBarHostState, - isVisible = isNotificationOpened.value, - containerColor = containerColor, - contentColor = contentColor, - backgroundImage = when (getMainViewModel().appSettings.value.backgroundImage) { - BackgroundImageOption.None -> null - BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) - BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) - }, - onDismiss = { - clearSnackBar() - isNotificationOpened.value = false - }, - onClick = { item -> - if (listOf(1, 2).contains(item.tag)) { - Intent(context, NewsActivity::class.java).also { - it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY - for (map1 in item.parameters) { - it.putExtra(map1.key, map1.value) - } - context.startActivity(it) - } - } - }, - onClear = { item -> - val item1 = item.clone() - getMainViewModel().notificationHistory.remove(item) - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString(R.string.notification_removed), - actionText = context.getString(R.string.action_undo), - action = { - getMainViewModel().notificationHistory.add(item1) - getMainViewModel().saveSettings() - } - ) - }, - onClearAll = { - showSnackBar( - text = context.getString(R.string.notification_removeall_confirm), - actionText = context.getString(R.string.action_confirm), - action = { - getMainViewModel().notificationHistory.clear() - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString(R.string.notification_removeall_removed), - clearPrevious = true - ) - }, - clearPrevious = true - ) - }, - opacity = getBackgroundAlpha() - ) - - BackHandler(isNotificationOpened.value) { - if (isNotificationOpened.value) { - isNotificationOpened.value = false - } - } +package io.zoemeow.dutschedule.ui.view.main + +import android.content.Context +import android.content.Intent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.GlobalVariables +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.activity.AccountActivity +import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.model.CustomClock +import io.zoemeow.dutschedule.model.ProcessState +import io.zoemeow.dutschedule.ui.component.main.AffectedLessonsSummaryItem +import io.zoemeow.dutschedule.ui.component.main.DateAndTimeSummaryItem +import io.zoemeow.dutschedule.ui.component.main.LessonTodaySummaryItem +import io.zoemeow.dutschedule.ui.component.main.SchoolNewsSummaryItem +import io.zoemeow.dutschedule.ui.component.main.UpdateAvailableSummaryItem +import io.zoemeow.dutschedule.utils.CustomDateUtil +import io.zoemeow.dutschedule.utils.openLink +import io.zoemeow.dutschedule.viewmodel.MainViewModel +import kotlinx.datetime.Clock +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toInstant +import kotlinx.datetime.toLocalDateTime +import kotlin.time.Duration.Companion.days + +// This for MainView - Tab View +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Activity_MainView_Dashboard( + modifier: Modifier = Modifier, + context: Context, + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onNewsOpened: (() -> Unit)? = null, + onLoginRequested: (() -> Unit)? = null +) { + Scaffold( + modifier = modifier.fillMaxWidth(), + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, + topBar = { + TopAppBar( + title = { Text(text = context.getString(R.string.app_name)) }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) + ) + }, + ) { paddingValues -> + Activity_MainView_Dashboard_Body( + modifier = Modifier.padding(paddingValues), + context = context, + appearanceState = appearanceState, + mainViewModel = mainViewModel, + onNewsOpened = onNewsOpened, + onLoginRequested = onLoginRequested + ) + } +} + +@Composable +fun Activity_MainView_Dashboard_Body( + modifier: Modifier = Modifier, + context: Context, + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onNewsOpened: (() -> Unit)? = null, + onLoginRequested: (() -> Unit)? = null +) { + Surface( + modifier = modifier.fillMaxWidth(), + color = Color.Transparent + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()) + ) { + DateAndTimeSummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + isLoading = mainViewModel.currentSchoolYearWeek.processState.value == ProcessState.Running, + currentSchoolWeek = mainViewModel.currentSchoolYearWeek.data.value, + opacity = appearanceState.componentOpacity + ) + LessonTodaySummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, + isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, + clicked = { + if (mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful) { + val intent = Intent(context, AccountActivity::class.java) + intent.action = AccountActivity.INTENT_SUBJECTINFORMATION + context.startActivity(intent) + } else { + onLoginRequested?.let { it() } + } + }, + affectedList = mainViewModel.accountSession.subjectSchedule.data.filter { subSch -> + subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && + subSch.subjectStudy.scheduleList.any { schItem -> + schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson + } + }.toList(), + opacity = appearanceState.componentOpacity + ) + AffectedLessonsSummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, + isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, + clicked = { + if (mainViewModel.accountSession.accountSession.processState.value != ProcessState.Successful) { + onLoginRequested?.let { it() } + } + }, + affectedList = arrayListOf( + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12" + ), + opacity = appearanceState.componentOpacity + ) + SchoolNewsSummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + newsToday = run { + val today = LocalDateTime( + Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, + LocalTime(0, 0, 0) + ).toInstant(TimeZone.UTC) + + // https://stackoverflow.com/questions/77368433/how-to-get-current-date-with-reset-time-0000-with-kotlinx-localdatetime + return@run mainViewModel.newsInstance.newsGlobal.data.filter { it.date == today.toEpochMilliseconds() }.size + }, + newsThisWeek = run { + val today = LocalDateTime( + Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date, + LocalTime(0, 0, 0) + ).toInstant(TimeZone.UTC) + val before7Days = today.minus(7.days) + + // https://stackoverflow.com/questions/77368433/how-to-get-current-date-with-reset-time-0000-with-kotlinx-localdatetime + return@run mainViewModel.newsInstance.newsGlobal.data.filter { it.date <= today.toEpochMilliseconds() && it.date >= before7Days.toEpochMilliseconds() }.size + }, + clicked = { + onNewsOpened?.let { it() } + }, + isLoading = mainViewModel.newsInstance.newsGlobal.processState.value == ProcessState.Running, + opacity = appearanceState.componentOpacity + ) + UpdateAvailableSummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + isLoading = false, + updateAvailable = false, + latestVersionString = "", + clicked = { + context.openLink( + url = GlobalVariables.LINK_REPOSITORY_RELEASE, + customTab = false, + ) + }, + opacity = appearanceState.componentOpacity + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt new file mode 100644 index 0000000..ce0390e --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt @@ -0,0 +1,277 @@ +package io.zoemeow.dutschedule.ui.view.main + +import android.content.Context +import android.content.Intent +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.AccountCircle +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.BottomAppBarDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.activity.MainActivity +import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.model.ProcessState +import io.zoemeow.dutschedule.model.settings.BackgroundImageOption +import io.zoemeow.dutschedule.utils.BackgroundImageUtil + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MainActivity.MainViewDashboard( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState, + newsClicked: (() -> Unit)? = null, + accountClicked: (() -> Unit)? = null, + settingsClicked: (() -> Unit)? = null, + externalLinkClicked: (() -> Unit)? = null +) { + val isNotificationOpened = remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, + topBar = { + TopAppBar( + title = { Text(text = context.getString(R.string.app_name)) }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) + ) + }, + bottomBar = { + BottomAppBar( + containerColor = BottomAppBarDefaults.containerColor.copy( + alpha = appearanceState.backgroundOpacity + ), + actions = { + BadgedBox( + // modifier = Modifier.padding(start = 15.dp, end = 15.dp), + badge = { + // Badge { } + } + ) { + IconButton( + onClick = { newsClicked?.let { it() } } + ) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_newspaper_24), + context.getString(R.string.news_title), + modifier = Modifier.size(27.dp) + ) + } + } + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + // Badge { } + } + ) { + IconButton( + onClick = { settingsClicked?.let { it() } } + ) { + Icon( + Icons.Default.Settings, + context.getString(R.string.settings_title), + modifier = Modifier.size(27.dp) + ) + } + } + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + // Badge { } + } + ) { + IconButton(onClick = { externalLinkClicked?.let { it() } }) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_web_24), + "External links", + modifier = Modifier.size(27.dp) + ) + } + } + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + if (getMainViewModel().notificationHistory.isNotEmpty()) { + Badge { + Text(getMainViewModel().notificationHistory.size.toString()) + } + } + }, + content = { + IconButton( + onClick = { + // Open notification bottom sheet + // Notification list requested + if (!isNotificationOpened.value) { + isNotificationOpened.value = true + } + } + ) { + Icon( + imageVector = Icons.Default.Notifications, + context.getString(R.string.notification_panel_title), + modifier = Modifier.size(27.dp), + ) + } + } + ) + }, + floatingActionButton = { + ExtendedFloatingActionButton( + text = { + Column( + modifier = Modifier.height(60.dp), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center, + content = { + Text( + context.getString(R.string.account_title), + style = MaterialTheme.typography.titleSmall + ) + getMainViewModel().accountSession.accountSession.processState.value.let { + Text( + when (it) { + ProcessState.NotRunYet -> context.getString(R.string.main_account_notloggedin) + ProcessState.Running -> context.getString(R.string.main_account_fetching) + else -> getMainViewModel().accountSession.accountSession.data.value?.accountAuth?.username ?: "unknown" + }, + style = MaterialTheme.typography.bodySmall + ) + } + } + ) + }, + icon = { + BadgedBox( + badge = { + if (getMainViewModel().accountSession.accountSession.processState.value == ProcessState.Failed) { + Badge { Text("!") } + } + }, + content = { + when (getMainViewModel().accountSession.accountSession.processState.value) { + ProcessState.Running -> CircularProgressIndicator( + modifier = Modifier.size(26.dp), + strokeWidth = 3.dp + ) + else -> Icon( + Icons.Outlined.AccountCircle, + context.getString(R.string.account_title), + modifier = Modifier.size(26.dp) + ) + } + } + ) + }, + onClick = { accountClicked?.let { it() } } + ) + } + ) + }, + content = { paddingValues -> + Activity_MainView_Dashboard_Body( + modifier = Modifier.padding(paddingValues), + context = context, + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onNewsOpened = { + context.startActivity(Intent(context, NewsActivity::class.java)) + } + ) + } + ) + NotificationScaffold( + context = context, + itemList = getMainViewModel().notificationHistory, + snackBarHostState = snackBarHostState, + isVisible = isNotificationOpened.value, + appearanceState = appearanceState, + backgroundImage = when (getMainViewModel().appSettings.value.backgroundImage) { + BackgroundImageOption.None -> null + BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) + BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) + }, + onDismiss = { + clearSnackBar() + isNotificationOpened.value = false + }, + onClick = { item -> + if (listOf(1, 2).contains(item.tag)) { + Intent(context, NewsActivity::class.java).also { + it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY + for (map1 in item.parameters) { + it.putExtra(map1.key, map1.value) + } + context.startActivity(it) + } + } + }, + onClear = { item -> + val item1 = item.clone() + getMainViewModel().notificationHistory.remove(item) + getMainViewModel().saveSettings() + showSnackBar( + text = context.getString(R.string.notification_removed), + actionText = context.getString(R.string.action_undo), + action = { + getMainViewModel().notificationHistory.add(item1) + getMainViewModel().saveSettings() + } + ) + }, + onClearAll = { + showSnackBar( + text = context.getString(R.string.notification_removeall_confirm), + actionText = context.getString(R.string.action_confirm), + action = { + getMainViewModel().notificationHistory.clear() + getMainViewModel().saveSettings() + showSnackBar( + text = context.getString(R.string.notification_removeall_removed), + clearPrevious = true + ) + }, + clearPrevious = true + ) + } + ) + + BackHandler(isNotificationOpened.value) { + if (isNotificationOpened.value) { + isNotificationOpened.value = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt similarity index 79% rename from app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt index d4e49e2..5aa1d2d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabbed.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt @@ -33,17 +33,17 @@ import androidx.navigation.compose.rememberNavController import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.MainActivity import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.NavBarItem -import io.zoemeow.dutschedule.ui.view.account.AccountMainView -import io.zoemeow.dutschedule.ui.view.news.NewsMainView +import io.zoemeow.dutschedule.ui.view.account.Activity_Account +import io.zoemeow.dutschedule.ui.view.news.Activity_News import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings @Composable fun MainActivity.MainViewTabbed( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, + appearanceState: AppearanceState ) { // Initialize for NavController for main activity val navController = rememberNavController() @@ -55,13 +55,13 @@ fun MainActivity.MainViewTabbed( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = Color.Transparent, - contentColor = contentColor, + contentColor = appearanceState.contentColor, // https://stackoverflow.com/questions/75328833/compose-scaffold-unnecessary-systembar-padding-due-to-windowcompat-setdecorfi contentWindowInsets = WindowInsets.navigationBars, bottomBar = { NavigationBar( - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, content = { NavBarItem.getAll().forEach( action = { @@ -95,20 +95,43 @@ fun MainActivity.MainViewTabbed( content = { NavHost( navController = navController, - startDestination = NavBarItem.news.route, - enterTransition = { fadeIn(animationSpec = tween(300)) }, - exitTransition = { fadeOut(animationSpec = tween(300)) }, - popEnterTransition = { fadeIn(animationSpec = tween(300)) }, - popExitTransition = { fadeOut(animationSpec = tween(300)) }, + startDestination = NavBarItem.dashboard.route, + enterTransition = { fadeIn(animationSpec = tween(200)) }, + exitTransition = { fadeOut(animationSpec = tween(200)) }, + popEnterTransition = { fadeIn(animationSpec = tween(200)) }, + popExitTransition = { fadeOut(animationSpec = tween(200)) }, modifier = Modifier.padding(it) ) { - // TODO: Add view here! + composable(NavBarItem.dashboard.route) { + Activity_MainView_Dashboard( + context = context, + appearanceState = appearanceState, + mainViewModel = getMainViewModel(), + onNewsOpened = { + navController.navigate(NavBarItem.news.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + }, + onLoginRequested = { + navController.navigate(NavBarItem.account.route) { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + } + ) + } + composable(NavBarItem.news.route) { - NewsMainView( + Activity_News( context = context, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getBackgroundAlpha(), + appearanceState = appearanceState, mainViewModel = getMainViewModel(), searchRequested = { val intent = Intent(context, NewsActivity::class.java) @@ -119,13 +142,11 @@ fun MainActivity.MainViewTabbed( } composable(NavBarItem.account.route) { - AccountMainView( + Activity_Account( context = context, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getBackgroundAlpha(), + appearanceState = appearanceState, mainViewModel = getMainViewModel(), - onShowSnackBar = { text, clearPrevious, actionText, action -> + onMessageReceived = { text, clearPrevious, actionText, action -> showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) } ) @@ -137,8 +158,7 @@ fun MainActivity.MainViewTabbed( itemList = getMainViewModel().notificationHistory.toList(), snackBarHostState = null, isVisible = true, - containerColor = containerColor, - contentColor = contentColor, + appearanceState = appearanceState, onClick = { item -> if (listOf(1, 2).contains(item.tag)) { Intent(context, NewsActivity::class.java).also { intent -> @@ -177,22 +197,19 @@ fun MainActivity.MainViewTabbed( }, clearPrevious = true ) - }, - opacity = getBackgroundAlpha() + } ) } composable(NavBarItem.settings.route) { Activity_Settings( context = context, - containerColor = containerColor, - contentColor = contentColor, - componentBackgroundAlpha = getBackgroundAlpha(), + appearanceState = appearanceState, mainViewModel = getMainViewModel(), mediaRequest = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, - onShowSnackBar = { text, clearPrevious, actionText, action -> + onMessageReceived = { text, clearPrevious, actionText, action -> showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) } ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/NotificationScaffold.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/NotificationScaffold.kt index e61964d..267f1ba 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/NotificationScaffold.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/NotificationScaffold.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.NotificationHistory import io.zoemeow.dutschedule.ui.component.main.NotificationItem import io.zoemeow.dutschedule.utils.CustomDateUtil @@ -44,23 +45,21 @@ import io.zoemeow.dutschedule.utils.CustomDateUtil @Composable fun NotificationScaffold( context: Context, - isVisible: Boolean = false, + snackBarHostState: SnackbarHostState? = null, + appearanceState: AppearanceState, itemList: List, - containerColor: Color, - contentColor: Color, backgroundImage: Bitmap? = null, - snackBarHostState: SnackbarHostState? = null, + isVisible: Boolean = false, onDismiss: (() -> Unit)? = null, onClick: ((NotificationHistory) -> Unit)? = null, onClear: ((NotificationHistory) -> Unit)? = null, - onClearAll: (() -> Unit)? = null, - opacity: Float = 1f, + onClearAll: (() -> Unit)? = null ) { AnimatedVisibility( visible = isVisible, enter = slideInVertically( initialOffsetY = { - it / 2 + it }, ), exit = slideOutVertically( @@ -78,8 +77,8 @@ fun NotificationScaffold( ) } Scaffold( - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { Text(text = context.getString(R.string.notification_panel_title)) }, @@ -142,7 +141,7 @@ fun NotificationScaffold( context = context, modifier = Modifier.padding(top = 2.dp, bottom = 5.dp), isVisible = true, - opacity = opacity, + opacity = appearanceState.componentOpacity, onClick = { onClick?.let { it(item) } }, onClear = { onClear?.let { it(item) } }, item = item diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt index 96e5256..5bf9119 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.news -import android.app.Activity.RESULT_CANCELED import android.content.Context import android.content.Intent import androidx.compose.foundation.ExperimentalFoundationApi @@ -46,6 +45,7 @@ import com.google.gson.Gson import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.news.NewsFetchType import io.zoemeow.dutschedule.ui.component.news.NewsListPage @@ -55,37 +55,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@Composable -fun NewsActivity.MainView( - context: Context, - snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - searchRequested: (() -> Unit)? = null -) { - NewsMainView( - context = context, - snackBarHostState = snackBarHostState, - containerColor = containerColor, - contentColor = contentColor, - searchRequested = searchRequested, - componentBackgroundAlpha = getBackgroundAlpha(), - mainViewModel = getMainViewModel(), - onBack = { - setResult(RESULT_CANCELED) - finish() - } - ) -} - @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable -fun NewsMainView( +fun Activity_News( context: Context, snackBarHostState: SnackbarHostState? = null, - containerColor: Color, - contentColor: Color, - componentBackgroundAlpha: Float = 1f, + appearanceState: AppearanceState, mainViewModel: MainViewModel, searchRequested: (() -> Unit)? = null, onBack: (() -> Unit)? = null @@ -96,8 +71,8 @@ fun NewsMainView( Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { Box( contentAlignment = Alignment.BottomCenter, @@ -238,7 +213,7 @@ fun NewsMainView( NewsListPage( newsList = mainViewModel.newsInstance.newsGlobal.data.toList(), processState = mainViewModel.newsInstance.newsGlobal.processState.value, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, itemClicked = { newsItem -> context.startActivity( Intent( @@ -264,11 +239,10 @@ fun NewsMainView( } 1 -> { - @Suppress("UNCHECKED_CAST") (NewsListPage( newsList = mainViewModel.newsInstance.newsSubject.data.toList() as List, processState = mainViewModel.newsInstance.newsSubject.processState.value, - opacity = componentBackgroundAlpha, + opacity = appearanceState.componentOpacity, itemClicked = { newsItem -> context.startActivity( Intent( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt index 46a0a32..745628b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt @@ -1,8 +1,6 @@ package io.zoemeow.dutschedule.ui.view.news -import android.app.Activity.RESULT_OK import android.content.Context -import androidx.activity.ComponentActivity import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -32,24 +30,26 @@ import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.ui.component.news.NewsDetailScreen @OptIn(ExperimentalMaterial3Api::class) @Composable -fun NewsActivity.NewsDetail( +fun Activity_News_NewsDetail( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + newsType: String? = null, + newsData: String? = null, + onLinkClicked: ((String) -> Unit)? = null, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onBack: () -> Unit ) { - val newsType = intent.getStringExtra("type") - val newsData = intent.getStringExtra("data") - Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { Text(context.getString(R.string.news_detail_title)) }, @@ -57,8 +57,7 @@ fun NewsActivity.NewsDetail( navigationIcon = { IconButton( onClick = { - setResult(RESULT_OK) - finish() + onBack() }, content = { Icon( @@ -83,20 +82,11 @@ fun NewsActivity.NewsDetail( }, onClick = { try { -// (Gson().fromJson(newsData, object : TypeToken() {}.type).also { -// getMainViewModel().appSettings.value.newsFilterList.add() -// } // TODO: Develop a add news filter function for news subject detail. - showSnackBar( - text = context.getString(R.string.feature_not_ready), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) } catch (ex: Exception) { ex.printStackTrace() - showSnackBar( - text = context.getString(R.string.news_detail_addtofilter_failed), - clearPrevious = true - ) + onMessageReceived(context.getString(R.string.news_detail_addtofilter_failed), true, null, null) } } ) @@ -111,11 +101,7 @@ fun NewsActivity.NewsDetail( newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), newsType = NewsType.Global, linkClicked = { link -> - openLink( - url = link, - context = this, - customTab = getMainViewModel().appSettings.value.openLinkInsideApp - ) + onLinkClicked?.let { it(link) } } ) } @@ -126,11 +112,7 @@ fun NewsActivity.NewsDetail( newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsGlobalItem, newsType = NewsType.Subject, linkClicked = { link -> - openLink( - url = link, - context = this, - customTab = getMainViewModel().appSettings.value.openLinkInsideApp - ) + onLinkClicked?.let { it(link) } } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt index 94b5d97..f0e2924 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt @@ -1,6 +1,5 @@ package io.zoemeow.dutschedule.ui.view.news -import android.app.Activity.RESULT_CANCELED import android.content.Context import android.content.Intent import androidx.activity.compose.BackHandler @@ -34,6 +33,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -41,6 +41,7 @@ import com.google.gson.Gson import io.dutwrapper.dutwrapper.model.enums.NewsType import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.news.NewsSearchOptionAndHistory import io.zoemeow.dutschedule.ui.component.news.NewsSearchResult @@ -48,15 +49,16 @@ import io.zoemeow.dutschedule.viewmodel.NewsSearchViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun NewsActivity.NewsSearch( +fun Activity_News_NewsSearch( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color + appearanceState: AppearanceState, + onBack: () -> Unit ) { val newsSearchViewModel: NewsSearchViewModel = viewModel() val lazyListState = rememberLazyListState() val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current val isSearchFocused: MutableTransitionState = remember { MutableTransitionState(false).apply { @@ -65,14 +67,14 @@ fun NewsActivity.NewsSearch( } fun dismissFocus() { - clearAllFocusAndHideKeyboard() + focusManager.clearFocus(force = true) isSearchFocused.targetState = false } Scaffold( snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { @@ -124,8 +126,7 @@ fun NewsActivity.NewsSearch( if (isSearchFocused.targetState) { dismissFocus() } else { - setResult(RESULT_CANCELED) - finish() + onBack() } }, content = { @@ -161,7 +162,7 @@ fun NewsActivity.NewsSearch( .padding(horizontal = 10.dp), newsList = newsSearchViewModel.newsList, lazyListState = lazyListState, - opacity = getBackgroundAlpha(), + opacity = appearanceState.componentOpacity, processState = newsSearchViewModel.progress.value, onEndOfList = { newsSearchViewModel.invokeSearch() diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt index 65a24b5..445951d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt @@ -20,17 +20,15 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState @OptIn(ExperimentalMaterial3Api::class) @Composable fun Activity_Settings_AboutApplication( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - onBackRequested: () -> Unit, - backgroundOpacity: Float = 1f, - controlOpacity: Float = 1f + appearanceState: AppearanceState, + onBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -38,8 +36,8 @@ fun Activity_Settings_AboutApplication( modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text("About") }, @@ -50,7 +48,7 @@ fun Activity_Settings_AboutApplication( navigationIcon = { IconButton( onClick = { - onBackRequested() + onBack() }, content = { Icon( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt index 33c9650..f992a3b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt @@ -30,13 +30,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.BaseActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.ui.component.base.DividerItem import io.zoemeow.dutschedule.ui.component.base.OptionItem import io.zoemeow.dutschedule.ui.component.base.OptionSwitchItem import io.zoemeow.dutschedule.ui.component.settings.ContentRegion import io.zoemeow.dutschedule.ui.component.settings.DialogSchoolYearSettings +import io.zoemeow.dutschedule.viewmodel.MainViewModel import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -44,9 +45,9 @@ import java.util.Locale fun Activity_Settings_ExperimentSettings( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - activity: BaseActivity, + appearanceState: AppearanceState, + mainViewModel: MainViewModel, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) onBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -56,8 +57,8 @@ fun Activity_Settings_ExperimentSettings( modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(context.getString(R.string.settings_experiment_title)) }, @@ -98,14 +99,14 @@ fun Activity_Settings_ExperimentSettings( title = context.getString(R.string.settings_experiment_option_currentschyear), description = context.getString( R.string.settings_experiment_option_currentschyear_description, - activity.getMainViewModel().appSettings.value.currentSchoolYear.year, - activity.getMainViewModel().appSettings.value.currentSchoolYear.year + 1, - when (activity.getMainViewModel().appSettings.value.currentSchoolYear.semester) { + mainViewModel.appSettings.value.currentSchoolYear.year, + mainViewModel.appSettings.value.currentSchoolYear.year + 1, + when (mainViewModel.appSettings.value.currentSchoolYear.semester) { 1 -> "1" 2 -> "2" else -> "2" }, - if (activity.getMainViewModel().appSettings.value.currentSchoolYear.semester > 2) " ${context.getString(R.string.settings_experiment_option_currentschyear_insummer)}" else "" + if (mainViewModel.appSettings.value.currentSchoolYear.semester > 2) " ${context.getString(R.string.settings_experiment_option_currentschyear_insummer)}" else "" ), onClick = { dialogSchoolYear.value = true @@ -125,13 +126,13 @@ fun Activity_Settings_ExperimentSettings( description = String.format( Locale.ROOT, "%2.0f%% %s", - (activity.getMainViewModel().appSettings.value.backgroundImageOpacity * 100), - if (activity.getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { + (mainViewModel.appSettings.value.backgroundImageOpacity * 100), + if (mainViewModel.appSettings.value.backgroundImage == BackgroundImageOption.None) { "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" } else "" ), onClick = { - activity.showSnackBar(context.getString(R.string.feature_not_ready), true) + onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) /* TODO: Implement here: Background opacity */ } ) @@ -141,13 +142,13 @@ fun Activity_Settings_ExperimentSettings( description = String.format( Locale.ROOT, "%2.0f%% %s", - (activity.getMainViewModel().appSettings.value.componentOpacity * 100), - if (activity.getMainViewModel().appSettings.value.backgroundImage == BackgroundImageOption.None) { + (mainViewModel.appSettings.value.componentOpacity * 100), + if (mainViewModel.appSettings.value.backgroundImage == BackgroundImageOption.None) { "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" } else "" ), onClick = { - activity.showSnackBar(context.getString(R.string.feature_not_ready), true) + onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) /* TODO: Implement here: Component opacity */ } ) @@ -157,38 +158,42 @@ fun Activity_Settings_ExperimentSettings( title = context.getString(R.string.settings_experiment_option_dashboardview), isVisible = true, isEnabled = true, - isChecked = activity.getMainViewModel().appSettings.value.mainScreenDashboardView, - description = when (activity.getMainViewModel().appSettings.value.mainScreenDashboardView) { + isChecked = mainViewModel.appSettings.value.mainScreenDashboardView, + description = when (mainViewModel.appSettings.value.mainScreenDashboardView) { true -> context.getString(R.string.settings_experiment_option_dashboardview_choice_enabled) false -> context.getString(R.string.settings_experiment_option_dashboardview_choice_disabled) }, onValueChanged = { - activity.showSnackBar( - text = context.getString( + onMessageReceived( + context.getString( R.string.settings_experiment_option_dashboardview_warning, - when (activity.getMainViewModel().appSettings.value.mainScreenDashboardView) { + when (mainViewModel.appSettings.value.mainScreenDashboardView) { true -> context.getString(R.string.settings_experiment_option_dashboardview_warning_disable) false -> context.getString(R.string.settings_experiment_option_dashboardview_warning_enable) } ), - clearPrevious = true, - actionText = context.getString(R.string.action_confirm), - action = { - activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( - mainScreenDashboardView = !activity.getMainViewModel().appSettings.value.mainScreenDashboardView + true, + context.getString(R.string.action_confirm) + ) { + mainViewModel.appSettings.value = + mainViewModel.appSettings.value.clone( + mainScreenDashboardView = !mainViewModel.appSettings.value.mainScreenDashboardView ) - activity.getMainViewModel().saveSettings( - onCompleted = { - val packageManager: PackageManager = context.packageManager - val intent: Intent = packageManager.getLaunchIntentForPackage(context.packageName)!! - val componentName: ComponentName = intent.component!! - val restartIntent: Intent = Intent.makeRestartActivityTask(componentName) - context.startActivity(restartIntent) - Runtime.getRuntime().exit(0) - } - ) - } - ) + mainViewModel.saveSettings( + onCompleted = { + val packageManager: PackageManager = + context.packageManager + val intent: Intent = + packageManager.getLaunchIntentForPackage(context.packageName)!! + val componentName: ComponentName = + intent.component!! + val restartIntent: Intent = + Intent.makeRestartActivityTask(componentName) + context.startActivity(restartIntent) + Runtime.getRuntime().exit(0) + } + ) + } } ) } @@ -204,7 +209,7 @@ fun Activity_Settings_ExperimentSettings( title = context.getString(R.string.settings_experiment_option_debuglog), description = context.getString(R.string.settings_experiment_option_debuglog_description), onClick = { - activity.showSnackBar(context.getString(R.string.feature_not_ready), true) + onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) /* TODO: Implement here: Debug log */ } ) @@ -218,12 +223,12 @@ fun Activity_Settings_ExperimentSettings( context = context, isVisible = dialogSchoolYear.value, dismissRequested = { dialogSchoolYear.value = false }, - currentSchoolYearItem = activity.getMainViewModel().appSettings.value.currentSchoolYear, + currentSchoolYearItem = mainViewModel.appSettings.value.currentSchoolYear, onSubmit = { - activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( + mainViewModel.appSettings.value = mainViewModel.appSettings.value.clone( currentSchoolYear = it ) - activity.getMainViewModel().saveSettings() + mainViewModel.saveSettings() dialogSchoolYear.value = false } ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt index ce99ff2..ead7321 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.os.LocaleListCompat import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -49,8 +50,7 @@ import java.util.Locale fun Activity_Settings_AppLanguageSettings( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, + appearanceState: AppearanceState, onBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -59,8 +59,8 @@ fun Activity_Settings_AppLanguageSettings( modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(context.getString(R.string.settings_applanguage_title)) }, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt index 1de3225..3d86a63 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt @@ -40,6 +40,7 @@ import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.PermissionsActivity import io.zoemeow.dutschedule.activity.SettingsActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.model.settings.ThemeMode import io.zoemeow.dutschedule.ui.component.base.DividerItem @@ -57,12 +58,10 @@ import java.util.Locale fun Activity_Settings( context: Context, snackBarHostState: SnackbarHostState? = null, - containerColor: Color, - contentColor: Color, - @Suppress("UNUSED_PARAMETER") componentBackgroundAlpha: Float = 1f, + appearanceState: AppearanceState, mainViewModel: MainViewModel, mediaRequest: () -> Unit, - onShowSnackBar: ((String, Boolean, String?, (() -> Unit)?) -> Unit)? = null, + onMessageReceived: ((String, Boolean, String?, (() -> Unit)?) -> Unit)? = null, onBack: (() -> Unit)? = null ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -74,8 +73,8 @@ fun Activity_Settings( modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(context.getString(R.string.settings_title)) }, @@ -330,7 +329,7 @@ fun Activity_Settings( BuildConfig.VERSION_CODE ), onClick = { - onShowSnackBar?.let { it(context.getString(R.string.feature_not_ready), true, null, null) } + onMessageReceived?.let { it(context.getString(R.string.feature_not_ready), true, null, null) } /* TODO: Implement here: Check for updates */ } ) @@ -413,7 +412,7 @@ fun Activity_Settings( backgroundImage = value ) } else { - onShowSnackBar?.let { + onMessageReceived?.let { it( context.getString(R.string.permission_missing_all_file_access), true, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt index 7242a19..04b9fd4 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/NewsNotificationSettings.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.SubjectCode import io.zoemeow.dutschedule.ui.component.base.CheckboxOption import io.zoemeow.dutschedule.ui.component.base.DividerItem @@ -64,8 +65,7 @@ import io.zoemeow.dutschedule.ui.component.settings.Dialog_Settings_NewsNotifica fun Activity_Settings_NewsNotificationSettings( context: Context, snackBarHostState: SnackbarHostState?, - containerColor: Color, - contentColor: Color, + appearanceState: AppearanceState, onBack: () -> Unit, fetchNewsInBackgroundDuration: Int = 0, onFetchNewsStateChanged: ((Int) -> Unit)? = null, @@ -78,8 +78,7 @@ fun Activity_Settings_NewsNotificationSettings( subjectFilterList: ArrayList = arrayListOf(), onSubjectFilterAdd: ((SubjectCode) -> Unit)? = null, onSubjectFilterDelete: ((SubjectCode) -> Unit)? = null, - onSubjectFilterClear: (() -> Unit)? = null, - opacity: Float = 1f + onSubjectFilterClear: (() -> Unit)? = null ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() val durationTemp = remember { @@ -99,8 +98,8 @@ fun Activity_Settings_NewsNotificationSettings( SnackbarHost(hostState = it) } }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(context.getString(R.string.settings_newsnotify_title)) }, @@ -151,7 +150,7 @@ fun Activity_Settings_NewsNotificationSettings( SimpleCardItem( padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), title = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_duration), - opacity = opacity, + opacity = appearanceState.componentOpacity, content = { Column( modifier = Modifier @@ -353,13 +352,13 @@ fun Activity_Settings_NewsNotificationSettings( Text(context.getString(R.string.settings_newsnotify_newsfilter_disabledwarning_description)) } }, - opacity = opacity + opacity = appearanceState.backgroundOpacity ) } else { SimpleCardItem( padding = PaddingValues(horizontal = 20.4.dp, vertical = 5.dp), title = context.getString(R.string.settings_newsnotify_newsfilter_list_title), - opacity = opacity, + opacity = appearanceState.backgroundOpacity, content = { Column( modifier = Modifier @@ -397,6 +396,7 @@ fun Activity_Settings_NewsNotificationSettings( } } ) + @Suppress("KotlinConstantConditions") OptionItem( modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), title = context.getString(R.string.settings_newsnotify_newsfilter_add), @@ -407,6 +407,7 @@ fun Activity_Settings_NewsNotificationSettings( dialogAddNew.value = true } ) + @Suppress("KotlinConstantConditions") OptionItem( modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), title = context.getString(R.string.settings_newsnotify_newsfilter_deleteall), diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt index a37805f..5fdad38 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ParseNewsSubjectNotification.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.activity.BaseActivity +import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.ui.component.base.SwitchWithTextInSurface @OptIn(ExperimentalMaterial3Api::class) @@ -41,9 +41,9 @@ import io.zoemeow.dutschedule.ui.component.base.SwitchWithTextInSurface fun Activity_Settings_ParseNewsSubjectNotification( context: Context, snackBarHostState: SnackbarHostState, - containerColor: Color, - contentColor: Color, - activity: BaseActivity, + appearanceState: AppearanceState, + isEnabled: Boolean = false, + onChange: () -> Unit, onBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -52,8 +52,8 @@ fun Activity_Settings_ParseNewsSubjectNotification( modifier = Modifier.fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = containerColor, - contentColor = contentColor, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( title = { Text(context.getString(R.string.settings_parsenewssubject_title)) }, @@ -91,7 +91,7 @@ fun Activity_Settings_ParseNewsSubjectNotification( .padding(bottom = 5.dp), shape = RoundedCornerShape(30.dp), color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = activity.getBackgroundAlpha() + alpha = appearanceState.componentOpacity ), content = { Column( @@ -99,7 +99,7 @@ fun Activity_Settings_ParseNewsSubjectNotification( .padding(20.dp), content = { Text( - when (activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { + when (isEnabled) { true -> context.getString(R.string.settings_parsenewssubject_preview_titleenabled) false -> context.getString(R.string.settings_parsenewssubject_preview_titledisabled) }, @@ -107,7 +107,7 @@ fun Activity_Settings_ParseNewsSubjectNotification( modifier = Modifier.padding(bottom = 5.dp) ) Text( - when (activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject) { + when (isEnabled) { true -> context.getString(R.string.settings_parsenewssubject_preview_descenabled) false -> context.getString(R.string.settings_parsenewssubject_preview_descdisabled) } @@ -119,12 +119,9 @@ fun Activity_Settings_ParseNewsSubjectNotification( SwitchWithTextInSurface( text = context.getString(R.string.settings_parsenewssubject_choice_enable), enabled = true, - checked = activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject, + checked = isEnabled, onCheckedChange = { - activity.getMainViewModel().appSettings.value = activity.getMainViewModel().appSettings.value.clone( - newsBackgroundParseNewsSubject = !activity.getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject - ) - activity.getMainViewModel().saveSettings() + onChange() } ) Column( diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d52df79..ffee1a6 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -41,6 +41,7 @@ Hôm nay Hôm qua + Tổng quan Chưa đăng nhập Đang tải… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68d4a8b..f39bc99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,6 +41,7 @@ Today Yesterday + Dashboard Not logged in Fetching… From e7582f4d9c0f9b58507f0808fb52f38005a4a7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:25:09 +0700 Subject: [PATCH 03/12] 2.0-draft19 (1551) - Update Project - Updated dependencies to latest. - Main view - Tab view edited to 3 tabs only (overview, news and account). - Deleted Notifications page. You can still open them using bell icon in Overview page. - Deleted Settings page. You can still open them using gear icon in Overview page. - This will remove main view - dashboard view in future. - New About page - This will move some items in Settings to this. --- app/build.gradle | 8 +- app/src/main/AndroidManifest.xml | 16 +- .../io/zoemeow/dutschedule/GlobalVariables.kt | 6 +- .../dutschedule/activity/AccountActivity.kt | 7 + .../dutschedule/activity/HelpActivity.kt | 221 ---------------- .../dutschedule/activity/MainActivity.kt | 2 +- .../activity/MiscellaneousActivity.kt | 53 ++++ .../dutschedule/activity/SettingsActivity.kt | 8 + .../zoemeow/dutschedule/model/NavBarItem.kt | 24 +- ...Clickable.kt => ClickableExternalLinks.kt} | 45 ++-- .../component/main/DateAndTimeSummaryItem.kt | 44 +++- .../ui/component/main/SummaryItem.kt | 21 +- .../ui/view/account/AccountInformation.kt | 16 +- .../dutschedule/ui/view/account/MainView.kt | 9 +- .../ui/view/main/MainViewDashboard.kt | 224 +++++++++++++--- .../ui/view/main/MainViewTabView.kt | 157 +++++++----- .../ui/view/miscellaneous/ExternalLinks.kt | 242 ++++++++++++++++++ .../dutschedule/ui/view/news/MainView.kt | 29 ++- .../dutschedule/ui/view/news/NewsDetail.kt | 15 +- .../ui/view/settings/AboutApplication.kt | 69 ----- .../ui/view/settings/AboutSettings.kt | 188 ++++++++++++++ .../dutschedule/ui/view/settings/MainView.kt | 44 +--- .../dutschedule/utils/FunctionExtension.kt | 5 + app/src/main/res/values-vi/strings.xml | 30 ++- app/src/main/res/values/strings.xml | 30 ++- 25 files changed, 980 insertions(+), 533 deletions(-) delete mode 100644 app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt create mode 100644 app/src/main/java/io/zoemeow/dutschedule/activity/MiscellaneousActivity.kt rename app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/{HelpLinkClickable.kt => ClickableExternalLinks.kt} (50%) create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/miscellaneous/ExternalLinks.kt delete mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutSettings.kt diff --git a/app/build.gradle b/app/build.gradle index 23ac586..96822cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 34 - versionCode 1526 + versionCode 1551 versionName "2.0-draft19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -55,7 +55,7 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.3' implementation 'androidx.activity:activity-compose:1.9.0' implementation platform('androidx.compose:compose-bom:2024.06.00') implementation "androidx.compose.ui:ui:1.6.8" @@ -77,9 +77,9 @@ dependencies { implementation 'androidx.navigation:navigation-compose:2.7.7' // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-ktx - runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2' + runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3' // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-compose - runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2' + runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3' // https://mvnrepository.com/artifact/androidx.fragment/fragment-ktx runtimeOnly 'androidx.fragment:fragment-ktx:1.8.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23460f6..176d8d8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -38,6 +38,7 @@ + - @@ -72,7 +72,7 @@ @@ -86,16 +86,16 @@ + android:windowSoftInputMode="adjustPan" /> @@ -107,7 +107,7 @@ android:enabled="true" android:exported="false" android:foregroundServiceType="shortService" - android:label="DutSchedule - News Update Service" /> + android:label="@string/activity_service_newsbackground" /> \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt b/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt index c898b84..036f6c5 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt @@ -6,8 +6,12 @@ class GlobalVariables { companion object { const val LINK_FORGOT_PASSWORD = "https://www.facebook.com/ctsvdhbkdhdn/posts/pfbid02G5sza1p8x7tEJ7S1Cac6a66EW3exgxLNmR9L26RZ8sX8xjhbEnguoeAXms31i7oxl" const val LINK_REPOSITORY = "https://github.com/ZoeMeow1027/DutSchedule" + const val LINK_REPOSITORY_LICENSE = "${LINK_REPOSITORY}/blob/stable/LICENSE" + const val LINK_REPOSITORY_CREDITS = "${LINK_REPOSITORY}?tab=readme-ov-file#credits-and-license" const val LINK_REPOSITORY_RELEASE = "${LINK_REPOSITORY}/releases" - const val LINK_CHANGELOG = "${LINK_REPOSITORY}/blob/stable/CHANGELOG.md" + const val LINK_REPOSITORY_CHANGELOG = "${LINK_REPOSITORY}/blob/stable/CHANGELOG.md" + + const val LICENSE_STRING = "MIT" const val REQUEST_EXPIRED_DURATION = 1000 * 60 * 5 val ROUNDED_CORNER_SHAPE_SIZE = 7.dp diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt index d012722..678502b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/AccountActivity.kt @@ -11,6 +11,7 @@ import io.zoemeow.dutschedule.ui.view.account.Activity_Account_SubjectInformatio import io.zoemeow.dutschedule.ui.view.account.Activity_Account_SubjectFee import io.zoemeow.dutschedule.ui.view.account.Activity_Account_TrainingResult import io.zoemeow.dutschedule.ui.view.account.Activity_Account_TrainingSubjectResult +import io.zoemeow.dutschedule.utils.openLink @AndroidEntryPoint class AccountActivity: BaseActivity() { @@ -80,6 +81,12 @@ class AccountActivity: BaseActivity() { action = action ) }, + onLinkClicked = { link -> + context.openLink( + url = link, + customTab = getMainViewModel().appSettings.value.openLinkInsideApp + ) + }, onBack = { setResult(RESULT_CANCELED) finish() diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt deleted file mode 100644 index 3bf9317..0000000 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/HelpActivity.kt +++ /dev/null @@ -1,221 +0,0 @@ -package io.zoemeow.dutschedule.activity - -import android.content.Context -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material.icons.filled.Info -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp -import dagger.hilt.android.AndroidEntryPoint -import io.zoemeow.dutschedule.model.AppearanceState -import io.zoemeow.dutschedule.model.HelpLinkInfo -import io.zoemeow.dutschedule.ui.component.helpandexternallink.HelpLinkClickable -import io.zoemeow.dutschedule.utils.openLink - -@AndroidEntryPoint -class HelpActivity : BaseActivity() { - @Composable - override fun OnPreloadOnce() { - - } - - @Composable - override fun OnMainView( - context: Context, - snackBarHostState: SnackbarHostState, - appearanceState: AppearanceState - ) { - when (intent.action) { - "view_externallink" -> { - ExternalLinkView( - context = context, - snackBarHostState = snackBarHostState, - appearanceState = appearanceState - ) - } - - else -> { - setResult(RESULT_CANCELED) - finish() - } - } - } - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - private fun ExternalLinkView( - context: Context, - snackBarHostState: SnackbarHostState, - appearanceState: AppearanceState - ) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - val searchText = remember { mutableStateOf("") } - - Scaffold( - modifier = Modifier.fillMaxSize(), - snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = appearanceState.containerColor, - contentColor = appearanceState.contentColor, - topBar = { - TopAppBar( - title = { Text(text = "External links - DUT School") }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), - navigationIcon = { - IconButton( - onClick = { - setResult(RESULT_OK) - finish() - }, - content = { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - "Back to previous screen", - modifier = Modifier.size(25.dp) - ) - } - ) - }, - ) - }, - content = { - Column( - modifier = Modifier - .padding(it) - .fillMaxSize() - .padding(horizontal = 20.dp) - .clickable { - focusManager.clearFocus(force = true) - }, - content = { - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 7.dp) - .clickable { - focusManager.clearFocus(force = true) - }, - color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = appearanceState.componentOpacity - ), - shape = RoundedCornerShape(7.dp), - content = { - Column( - modifier = Modifier.padding(horizontal = 15.dp, vertical = 15.dp) - ) { - Icon( - Icons.Default.Info, - contentDescription = "", - modifier = Modifier.padding(bottom = 7.dp) - ) - Text( - "Note: This page will show most used links related to DUT school, so it could be missed here. You can navigate to official DUT home page for more information." - ) - } - } - ) - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 20.dp) - .focusRequester(focusRequester), - value = searchText.value, - onValueChange = { searchVal -> searchText.value = searchVal }, - label = { - Text("Search a external link") - }, - trailingIcon = { - if (searchText.value.isNotEmpty()) { - IconButton( - onClick = { - searchText.value = "" - focusRequester.requestFocus() - }, - content = { - Icon( - Icons.Default.Clear, - contentDescription = "Clear query", - modifier = Modifier.size(24.dp) - ) - } - ) - } - }, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus(force = true) - } - ) - ) - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()) - .clickable { - focusManager.clearFocus(force = true) - }, - content = { - HelpLinkInfo.getAllExternalLink().filter { item -> - searchText.value.isEmpty() || - item.title.lowercase().contains(searchText.value.lowercase()) || - item.description?.lowercase()?.contains(searchText.value.lowercase()) ?: false || - item.url.lowercase().contains(searchText.value.lowercase()) - }.toList().forEach { item -> - HelpLinkClickable( - item = item, - modifier = Modifier.fillMaxWidth().padding(bottom = 7.dp), - opacity = appearanceState.componentOpacity, - linkClicked = { - focusManager.clearFocus(force = true) - try { - context.openLink( - url = item.url, - customTab = getMainViewModel().appSettings.value.openLinkInsideApp - ) - } catch (ex: Exception) { - ex.printStackTrace() - } - } - ) - } - } - ) - } - ) - } - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt index c096749..f2a314e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt @@ -78,7 +78,7 @@ class MainActivity : BaseActivity() { context.startActivity(Intent(context, SettingsActivity::class.java)) }, externalLinkClicked = { - val intent = Intent(context, HelpActivity::class.java) + val intent = Intent(context, MiscellaneousActivity::class.java) intent.action = "view_externallink" context.startActivity(intent) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/MiscellaneousActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/MiscellaneousActivity.kt new file mode 100644 index 0000000..048ac09 --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/MiscellaneousActivity.kt @@ -0,0 +1,53 @@ +package io.zoemeow.dutschedule.activity + +import android.content.Context +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.ui.view.miscellaneous.Activity_Miscellaneous_ExternalLinks +import io.zoemeow.dutschedule.utils.openLink + +@AndroidEntryPoint +class MiscellaneousActivity : BaseActivity() { + companion object { + const val INTENT_EXTERNALLINKS = "view_externallink" + } + + @Composable + override fun OnPreloadOnce() { + + } + + @Composable + override fun OnMainView( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState + ) { + when (intent.action) { + INTENT_EXTERNALLINKS -> { + Activity_Miscellaneous_ExternalLinks( + context = context, + snackBarHostState = snackBarHostState, + appearanceState = appearanceState, + onLinkClicked = { link -> + context.openLink( + url = link, + customTab = getMainViewModel().appSettings.value.openLinkInsideApp + ) + }, + onBack = { + setResult(RESULT_CANCELED) + finish() + } + ) + } + + else -> { + setResult(RESULT_CANCELED) + finish() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt index 037ccc4..66131ee 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt @@ -8,6 +8,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption @@ -18,6 +19,7 @@ import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ExperimentSetti import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_NewsNotificationSettings import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ParseNewsSubjectNotification import io.zoemeow.dutschedule.utils.BackgroundImageUtil +import io.zoemeow.dutschedule.utils.openLink @AndroidEntryPoint class SettingsActivity : BaseActivity() { @@ -274,6 +276,12 @@ class SettingsActivity : BaseActivity() { context = context, snackBarHostState = snackBarHostState, appearanceState = appearanceState, + onLinkClicked = { link -> + context.openLink( + url = link, + customTab = getMainViewModel().appSettings.value.openLinkInsideApp, + ) + }, onBack = { setResult(RESULT_CANCELED) finish() diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt b/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt index 6b796be..33d016d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/NavBarItem.kt @@ -3,8 +3,6 @@ package io.zoemeow.dutschedule.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Notifications -import androidx.compose.material.icons.filled.Settings import androidx.compose.ui.graphics.vector.ImageVector import io.zoemeow.dutschedule.R @@ -33,20 +31,20 @@ data class NavBarItem( route = "account" ) - val notification = NavBarItem( - titleResId = R.string.notification_panel_title, - icon = Icons.Default.Notifications, - route = "notifications" - ) +// val notification = NavBarItem( +// titleResId = R.string.notification_panel_title, +// icon = Icons.Default.Notifications, +// route = "notifications" +// ) - val settings = NavBarItem( - titleResId = R.string.settings_title, - icon = Icons.Default.Settings, - route = "settings" - ) +// val settings = NavBarItem( +// titleResId = R.string.settings_title, +// icon = Icons.Default.Settings, +// route = "settings" +// ) fun getAll(): List { - return listOf(dashboard, news, account, notification, settings) + return listOf(dashboard, news, account) } } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/HelpLinkClickable.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/ClickableExternalLinks.kt similarity index 50% rename from app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/HelpLinkClickable.kt rename to app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/ClickableExternalLinks.kt index 7be17d2..4211539 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/HelpLinkClickable.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/helpandexternallink/ClickableExternalLinks.kt @@ -2,7 +2,9 @@ package io.zoemeow.dutschedule.ui.component.helpandexternallink import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -13,34 +15,33 @@ import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.model.HelpLinkInfo @Composable -fun HelpLinkClickable( +fun ClickableExternalLinks( item: HelpLinkInfo, modifier: Modifier = Modifier, opacity: Float = 1f, - linkClicked: (() -> Unit)? = null + onClick: (() -> Unit)? = null ) { Surface( modifier = modifier - .clickable { linkClicked?.let { it() } }, - color = MaterialTheme.colorScheme.secondaryContainer.copy( - alpha = opacity - ), - shape = RoundedCornerShape(10.dp), - content = { - Column( - modifier = Modifier.padding(horizontal = 15.dp, vertical = 10.dp), - content = { - Text( - item.title, - style = MaterialTheme.typography.titleLarge - ) - Text( - item.url, - modifier = Modifier.padding(bottom = 15.dp) - ) - Text(item.description ?: "(no description provided)") - } + .run { + if (onClick != null) return@run this.clickable { onClick() } + else return@run this + }, + color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = opacity), + shape = RoundedCornerShape(10.dp) + ) { + Column( + modifier = Modifier.padding(horizontal = 15.dp, vertical = 10.dp) + ) { + Text( + item.title, + style = MaterialTheme.typography.titleMedium ) + Text(item.url) + item.description?.let { + Spacer(modifier = Modifier.size(15.dp)) + Text(item.description) + } } - ) + } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt index 38d1d6b..04eb88b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt @@ -1,15 +1,18 @@ package io.zoemeow.dutschedule.ui.component.main +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem @@ -29,7 +32,7 @@ fun DateAndTimeSummaryItem( SummaryItem( padding = padding, title = "Date & time today", - isLoading = isLoading, + isLoading = false, opacity = opacity, clicked = { }, content = { @@ -41,18 +44,33 @@ fun DateAndTimeSummaryItem( style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(horizontal = 15.dp) ) - Text( - text = String.format( - "School year: %s - Week: %s\nCurrent lesson: %s", - currentSchoolWeek?.schoolYear ?: "(unknown)", - currentSchoolWeek?.week?.toString() ?: "(unknown)", - CustomClock.getCurrent().toDUTLesson2().name - ), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier - .padding(horizontal = 15.dp) - .padding(bottom = 10.dp) - ) + when (isLoading) { + true -> { + Box( + modifier = Modifier.fillMaxWidth() + .padding(horizontal = 15.dp) + .padding(bottom = 10.dp), + contentAlignment = Alignment.Center, + content = { + CircularProgressIndicator() + } + ) + } + false -> { + Text( + text = String.format( + "School year: %s - Week: %s\nCurrent lesson: %s", + currentSchoolWeek?.schoolYear ?: "(unknown)", + currentSchoolWeek?.week?.toString() ?: "(unknown)", + CustomClock.getCurrent().toDUTLesson2().name + ), + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .padding(horizontal = 15.dp) + .padding(bottom = 10.dp) + ) + } + } } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt index 5ce4089..8534d37 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt @@ -1,19 +1,13 @@ package io.zoemeow.dutschedule.ui.component.main -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.ui.component.base.SimpleCardItem @@ -33,15 +27,10 @@ fun SummaryItem( opacity = opacity, content = { if (isLoading) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(padding) - .clip(RoundedCornerShape(7.dp)) - .background(MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0f)), + Box( + modifier = Modifier.fillMaxWidth() + .padding(bottom = 10.dp), + contentAlignment = Alignment.Center ) { CircularProgressIndicator() } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt index eb90686..7f3771b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/AccountInformation.kt @@ -49,6 +49,7 @@ import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.ui.component.base.OutlinedTextBox +import io.zoemeow.dutschedule.utils.openLink import io.zoemeow.dutschedule.viewmodel.MainViewModel import kotlinx.coroutines.launch @@ -59,11 +60,13 @@ fun Activity_Account_AccountInformation( snackBarHostState: SnackbarHostState, appearanceState: AppearanceState, mainViewModel: MainViewModel, + onLinkClicked: ((String) -> Unit)? = null, onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) onBack: () -> Unit ) { val clipboardManager: ClipboardManager = LocalClipboardManager.current val scope = rememberCoroutineScope() + Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, @@ -98,7 +101,10 @@ fun Activity_Account_AccountInformation( ) }, bottomBar = { - val pageInfoTooltipState = rememberTooltipState(isPersistent = true) + val pageInfoTooltipState = rememberTooltipState( + isPersistent = true, + initialIsVisible = true + ) BottomAppBar( floatingActionButton = { if (mainViewModel.accountSession.accountInformation.processState.value != ProcessState.Running) { @@ -123,6 +129,14 @@ fun Activity_Account_AccountInformation( title = { Text(context.getString(R.string.account_accinfo_editinfo)) }, text = { Text(context.getString(R.string.account_accinfo_description)) + }, + action = { + TextButton( + onClick = { onLinkClicked?.let { it("http://sv.dut.udn.vn") } }, + content = { + Text(context.getString(R.string.account_accinfo_action_openlink)) + } + ) } ) }, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt index e3206b2..6fb37a8 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/MainView.kt @@ -17,6 +17,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -28,6 +29,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R @@ -60,14 +62,17 @@ fun Activity_Account( val logoutDialogVisible = remember { mutableStateOf(false) } Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxWidth(), snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, containerColor = appearanceState.containerColor, contentColor = appearanceState.contentColor, topBar = { TopAppBar( title = { Text(context.getString(R.string.account_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { if (onBack != null) { IconButton( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index 9c8e280..69917f5 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -6,24 +6,35 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Notifications +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Badge +import androidx.compose.material3.BadgedBox import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.AccountActivity -import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.CustomClock import io.zoemeow.dutschedule.model.ProcessState @@ -31,6 +42,7 @@ import io.zoemeow.dutschedule.ui.component.main.AffectedLessonsSummaryItem import io.zoemeow.dutschedule.ui.component.main.DateAndTimeSummaryItem import io.zoemeow.dutschedule.ui.component.main.LessonTodaySummaryItem import io.zoemeow.dutschedule.ui.component.main.SchoolNewsSummaryItem +import io.zoemeow.dutschedule.ui.component.main.SummaryItem import io.zoemeow.dutschedule.ui.component.main.UpdateAvailableSummaryItem import io.zoemeow.dutschedule.utils.CustomDateUtil import io.zoemeow.dutschedule.utils.openLink @@ -51,9 +63,17 @@ fun Activity_MainView_Dashboard( context: Context, appearanceState: AppearanceState, mainViewModel: MainViewModel, + notificationCount: Int = 0, + onExternalLinkClicked: (() -> Unit)? = null, + onNotificationPanelRequested: (() -> Unit)? = null, + onSettingsRequested: (() -> Unit)? = null, onNewsOpened: (() -> Unit)? = null, onLoginRequested: (() -> Unit)? = null ) { + val notificationTooltipState = rememberTooltipState() + val relatedLinkTooltipState = rememberTooltipState() + val settingsTooltipState = rememberTooltipState() + Scaffold( modifier = modifier.fillMaxWidth(), containerColor = appearanceState.containerColor, @@ -61,7 +81,101 @@ fun Activity_MainView_Dashboard( topBar = { TopAppBar( title = { Text(text = context.getString(R.string.app_name)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), + actions = { + onNotificationPanelRequested?.let { onClick -> + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.notification_panel_title)) + } + }, + state = notificationTooltipState, + content = { + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + if (notificationCount > 0) { + Badge { Text(notificationCount.toString()) } + } + }, + content = { + IconButton( + // Open notification bottom sheet - Notification list requested + onClick = { onClick() } + ) { + Icon( + imageVector = Icons.Default.Notifications, + context.getString(R.string.notification_panel_title), + modifier = Modifier.size(27.dp), + ) + } + } + ) + } + ) + } + onExternalLinkClicked?.let { + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.miscellaneous_externallinks_mainviewbutton)) + } + }, + state = relatedLinkTooltipState, + content = { + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + // Badge { } + } + ) { + IconButton(onClick = { it() }) { + Icon( + painter = painterResource(id = R.drawable.ic_baseline_web_24), + context.getString(R.string.miscellaneous_externallinks_mainviewbutton), + modifier = Modifier.size(27.dp) + ) + } + } + } + ) + } + onSettingsRequested?.let { + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.settings_title)) + } + }, + state = settingsTooltipState, + content = { + BadgedBox( + // modifier = Modifier.padding(end = 15.dp), + badge = { + // Badge { } + } + ) { + IconButton( + onClick = { it() } + ) { + Icon( + Icons.Default.Settings, + context.getString(R.string.settings_title), + modifier = Modifier.size(27.dp) + ) + } + } + } + ) + } + } ) }, ) { paddingValues -> @@ -100,45 +214,71 @@ fun Activity_MainView_Dashboard_Body( currentSchoolWeek = mainViewModel.currentSchoolYearWeek.data.value, opacity = appearanceState.componentOpacity ) - LessonTodaySummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, - isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, - clicked = { - if (mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful) { - val intent = Intent(context, AccountActivity::class.java) - intent.action = AccountActivity.INTENT_SUBJECTINFORMATION - context.startActivity(intent) - } else { - onLoginRequested?.let { it() } - } - }, - affectedList = mainViewModel.accountSession.subjectSchedule.data.filter { subSch -> - subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && - subSch.subjectStudy.scheduleList.any { schItem -> - schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson + when (mainViewModel.accountSession.accountSession.processState.value) { + ProcessState.Successful, + ProcessState.Failed -> { + LessonTodaySummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, + isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, + clicked = { + if (mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful) { + val intent = Intent(context, AccountActivity::class.java) + intent.action = AccountActivity.INTENT_SUBJECTINFORMATION + context.startActivity(intent) + } else { + onLoginRequested?.let { it() } } - }.toList(), - opacity = appearanceState.componentOpacity - ) - AffectedLessonsSummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, - isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, - clicked = { - if (mainViewModel.accountSession.accountSession.processState.value != ProcessState.Successful) { - onLoginRequested?.let { it() } - } - }, - affectedList = arrayListOf( - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12" - ), - opacity = appearanceState.componentOpacity - ) + }, + affectedList = mainViewModel.accountSession.subjectSchedule.data.filter { subSch -> + subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && + subSch.subjectStudy.scheduleList.any { schItem -> + schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson + } + }.toList(), + opacity = appearanceState.componentOpacity + ) + AffectedLessonsSummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, + isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, + clicked = { + if (mainViewModel.accountSession.accountSession.processState.value != ProcessState.Successful) { + onLoginRequested?.let { it() } + } + }, + affectedList = arrayListOf( + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12", + "ie1i0921d - i029di12" + ), + opacity = appearanceState.componentOpacity + ) + } + ProcessState.NotRunYet -> { + // TODO: Toast for not logged in here + SummaryItem( + padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), + title = "Login-only widgets", + opacity = appearanceState.componentOpacity, + clicked = { + onLoginRequested?.let { it() } + }, + content = { + Text( + text = "Lessons in today and affected lessons widget will available after logged in. To do so, click here to navigate to login page.", + style = MaterialTheme.typography.bodyMedium, + modifier = Modifier + .padding(horizontal = 15.dp) + .padding(bottom = 10.dp) + ) + } + ) + } + else -> { } + } SchoolNewsSummaryItem( padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), newsToday = run { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt index 5aa1d2d..9003fb1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt @@ -2,8 +2,7 @@ package io.zoemeow.dutschedule.ui.view.main import android.content.Context import android.content.Intent -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.compose.BackHandler import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -22,6 +21,8 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -31,13 +32,16 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.activity.MiscellaneousActivity import io.zoemeow.dutschedule.activity.MainActivity import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.NavBarItem +import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.ui.view.account.Activity_Account import io.zoemeow.dutschedule.ui.view.news.Activity_News -import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings +import io.zoemeow.dutschedule.utils.BackgroundImageUtil @Composable fun MainActivity.MainViewTabbed( @@ -51,6 +55,8 @@ fun MainActivity.MainViewTabbed( val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route + val isNotificationOpened = remember { mutableStateOf(false) } + Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, @@ -107,6 +113,18 @@ fun MainActivity.MainViewTabbed( context = context, appearanceState = appearanceState, mainViewModel = getMainViewModel(), + notificationCount = getMainViewModel().notificationHistory.size, + onNotificationPanelRequested = { + isNotificationOpened.value = true + }, + onExternalLinkClicked = { + val intent = Intent(context, MiscellaneousActivity::class.java) + intent.action = MiscellaneousActivity.INTENT_EXTERNALLINKS + context.startActivity(intent) + }, + onSettingsRequested = { + context.startActivity(Intent(context, SettingsActivity::class.java)) + }, onNewsOpened = { navController.navigate(NavBarItem.news.route) { popUpTo(navController.graph.findStartDestination().id) { @@ -152,69 +170,80 @@ fun MainActivity.MainViewTabbed( ) } - composable(NavBarItem.notification.route) { - NotificationScaffold( - context = context, - itemList = getMainViewModel().notificationHistory.toList(), - snackBarHostState = null, - isVisible = true, - appearanceState = appearanceState, - onClick = { item -> - if (listOf(1, 2).contains(item.tag)) { - Intent(context, NewsActivity::class.java).also { intent -> - intent.action = NewsActivity.INTENT_NEWSDETAILACTIVITY - for (map1 in item.parameters) { - intent.putExtra(map1.key, map1.value) - } - context.startActivity(intent) - } - } - }, - onClear = { item -> - val itemTemp = item.clone() - getMainViewModel().notificationHistory.remove(item) - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString(R.string.notification_removed), - actionText = context.getString(R.string.action_undo), - action = { - getMainViewModel().notificationHistory.add(itemTemp) - getMainViewModel().saveSettings() - } - ) - }, - onClearAll = { - showSnackBar( - text = context.getString(R.string.notification_removeall_confirm), - actionText = context.getString(R.string.action_confirm), - action = { - getMainViewModel().saveSettings() - getMainViewModel().notificationHistory.clear() - showSnackBar( - text = context.getString(R.string.notification_removeall_removed), - clearPrevious = true - ) - }, - clearPrevious = true - ) - } - ) - } - - composable(NavBarItem.settings.route) { - Activity_Settings( - context = context, - appearanceState = appearanceState, - mainViewModel = getMainViewModel(), - mediaRequest = { - pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) - }, - onMessageReceived = { text, clearPrevious, actionText, action -> - showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) - } - ) +// composable(NavBarItem.settings.route) { +// Activity_Settings( +// context = context, +// appearanceState = appearanceState, +// mainViewModel = getMainViewModel(), +// mediaRequest = { +// pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) +// }, +// onMessageReceived = { text, clearPrevious, actionText, action -> +// showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) +// } +// ) +// } + } + } + ) + NotificationScaffold( + context = context, + itemList = getMainViewModel().notificationHistory, + snackBarHostState = snackBarHostState, + isVisible = isNotificationOpened.value, + appearanceState = appearanceState, + backgroundImage = when (getMainViewModel().appSettings.value.backgroundImage) { + BackgroundImageOption.None -> null + BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) + BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) + }, + onDismiss = { + clearSnackBar() + isNotificationOpened.value = false + }, + onClick = { item -> + if (listOf(1, 2).contains(item.tag)) { + Intent(context, NewsActivity::class.java).also { + it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY + for (map1 in item.parameters) { + it.putExtra(map1.key, map1.value) + } + context.startActivity(it) } } + }, + onClear = { item -> + val item1 = item.clone() + getMainViewModel().notificationHistory.remove(item) + getMainViewModel().saveSettings() + showSnackBar( + text = context.getString(R.string.notification_removed), + actionText = context.getString(R.string.action_undo), + action = { + getMainViewModel().notificationHistory.add(item1) + getMainViewModel().saveSettings() + } + ) + }, + onClearAll = { + showSnackBar( + text = context.getString(R.string.notification_removeall_confirm), + actionText = context.getString(R.string.action_confirm), + action = { + getMainViewModel().notificationHistory.clear() + getMainViewModel().saveSettings() + showSnackBar( + text = context.getString(R.string.notification_removeall_removed), + clearPrevious = true + ) + }, + clearPrevious = true + ) } ) + BackHandler(isNotificationOpened.value) { + if (isNotificationOpened.value) { + isNotificationOpened.value = false + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/miscellaneous/ExternalLinks.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/miscellaneous/ExternalLinks.kt new file mode 100644 index 0000000..bb8cd73 --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/miscellaneous/ExternalLinks.kt @@ -0,0 +1,242 @@ +package io.zoemeow.dutschedule.ui.view.miscellaneous + +import android.content.Context +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.RichTooltip +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTooltipState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.model.HelpLinkInfo +import io.zoemeow.dutschedule.ui.component.helpandexternallink.ClickableExternalLinks +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Activity_Miscellaneous_ExternalLinks( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState, + onLinkClicked: (String) -> Unit, + onBack: () -> Unit +) { + val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current + val scope = rememberCoroutineScope() + + val searchEnabled = remember { mutableStateOf(false) } + val searchText = remember { mutableStateOf("") } + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, + topBar = { + TopAppBar( + title = { Text(context.getString(R.string.miscellaneous_externallinks_title)) }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + navigationIcon = { + IconButton( + onClick = { onBack() }, + content = { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + context.getString(R.string.action_back), + modifier = Modifier.size(25.dp) + ) + } + ) + }, + ) + }, + bottomBar = { + val pageInfoTooltipState = rememberTooltipState( + isPersistent = true, + initialIsVisible = true + ) + BottomAppBar( + actions = { + TextButton( + onClick = { + searchEnabled.value = !searchEnabled.value + }, + content = { + Row(verticalAlignment = Alignment.CenterVertically) { + Spacer(modifier = Modifier.size(5.dp)) + Icon(ImageVector.vectorResource(id = R.drawable.ic_baseline_filter_list_alt_24), context.getString(R.string.action_search)) + Spacer(modifier = Modifier.size(3.dp)) + Text(context.getString(R.string.miscellaneous_externallinks_action_search)) + Spacer(modifier = Modifier.size(5.dp)) + } + }, + colors = ButtonDefaults.buttonColors().copy( + containerColor = if (!searchEnabled.value) Color.Transparent else MaterialTheme.colorScheme.primary.copy(alpha = 0.7f), + contentColor = if (!searchEnabled.value) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimary + ) + ) + TooltipBox( + positionProvider = TooltipDefaults.rememberRichTooltipPositionProvider(), + tooltip = { + RichTooltip( + title = { Text(context.getString(R.string.miscellaneous_externallinks_action_aboutrelatedlink)) }, + text = { + Text(context.getString(R.string.miscellaneous_externallinks_tooltip_aboutrelatedlink)) + } + ) + }, + state = pageInfoTooltipState, + enableUserInput = false, + content = { + TextButton( + onClick = { + scope.launch { + if (pageInfoTooltipState.isVisible) { + pageInfoTooltipState.dismiss() + } + pageInfoTooltipState.show() + } + }, + content = { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Info, context.getString(R.string.tooltip_info)) + Spacer(modifier = Modifier.size(5.dp)) + Text(context.getString(R.string.miscellaneous_externallinks_action_aboutrelatedlink)) + } + } + ) + } + ) + }, + containerColor = Color.Transparent + ) + }, + content = { paddingValues -> + Scaffold( + modifier = Modifier.padding(paddingValues), + contentWindowInsets = WindowInsets(top = 0.dp), + containerColor = Color.Transparent, + bottomBar = { + AnimatedVisibility( + visible = searchEnabled.value, + enter = slideInVertically(initialOffsetY = { it } ), + exit = slideOutVertically(targetOffsetY = { it }) + ) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 10.dp) + .focusRequester(focusRequester), + value = searchText.value, + label = { Text(context.getString(R.string.miscellaneous_externallinks_searchaexternallink)) }, + onValueChange = { + if (searchEnabled.value) { + searchText.value = it + } + }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions( + onDone = { + focusManager.clearFocus(force = true) + } + ), + trailingIcon = { + if (searchText.value.isNotEmpty()) { + IconButton( + onClick = { searchText.value = "" }, + content = { + Icon( + Icons.Default.Clear, + context.getString(R.string.action_clear) + ) + } + ) + } + } + ) + } + } + ) { paddingValues2 -> + Column( + modifier = Modifier + .padding(paddingValues2) + .fillMaxSize() + .padding(horizontal = 10.dp) + .verticalScroll(rememberScrollState()) + ) { + HelpLinkInfo.getAllExternalLink().filter { item -> + searchText.value.isEmpty() || + item.title.lowercase().contains(searchText.value.lowercase()) || + item.description?.lowercase()?.contains(searchText.value.lowercase()) ?: false || + item.url.lowercase().contains(searchText.value.lowercase()) + }.toList().forEach { item -> + ClickableExternalLinks( + item = item, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 7.dp), + opacity = appearanceState.componentOpacity, + onClick = { + focusManager.clearFocus(force = true) + try { + onLinkClicked(item.url) + } catch (ex: Exception) { + ex.printStackTrace() + } + } + ) + } + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt index 5bf9119..43e81ac 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Scaffold import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButtonDefaults @@ -32,8 +33,11 @@ import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -79,7 +83,10 @@ fun Activity_News( content = { TopAppBar( title = { Text(text = context.getString(R.string.news_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { if (onBack != null) { IconButton( @@ -97,12 +104,24 @@ fun Activity_News( } }, actions = { - IconButton( - onClick = { - searchRequested?.let { it() } + val newsSearchTooltipSearch = rememberTooltipState() + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.news_action_search)) + } }, + state = newsSearchTooltipSearch, content = { - Icon(Icons.Default.Search, context.getString(R.string.action_search)) + IconButton( + onClick = { + searchRequested?.let { it() } + }, + content = { + Icon(Icons.Default.Search, context.getString(R.string.news_action_search)) + } + ) } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt index 745628b..7fc0360 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt @@ -12,6 +12,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -22,6 +23,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import com.google.gson.Gson import com.google.gson.reflect.TypeToken @@ -45,15 +47,21 @@ fun Activity_News_NewsDetail( onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) onBack: () -> Unit ) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = appearanceState.containerColor, contentColor = appearanceState.contentColor, topBar = { - TopAppBar( + LargeTopAppBar( title = { Text(context.getString(R.string.news_detail_title)) }, - colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent), + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), navigationIcon = { IconButton( onClick = { @@ -68,6 +76,7 @@ fun Activity_News_NewsDetail( } ) }, + scrollBehavior = scrollBehavior ) }, floatingActionButton = { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt deleted file mode 100644 index 445951d..0000000 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutApplication.kt +++ /dev/null @@ -1,69 +0,0 @@ -package io.zoemeow.dutschedule.ui.view.settings - -import android.content.Context -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LargeTopAppBar -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.unit.dp -import io.zoemeow.dutschedule.R -import io.zoemeow.dutschedule.model.AppearanceState - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun Activity_Settings_AboutApplication( - context: Context, - snackBarHostState: SnackbarHostState, - appearanceState: AppearanceState, - onBack: () -> Unit -) { - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - - Scaffold( - modifier = Modifier.fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection), - snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, - containerColor = appearanceState.containerColor, - contentColor = appearanceState.contentColor, - topBar = { - LargeTopAppBar( - title = { Text("About") }, - colors = TopAppBarDefaults.largeTopAppBarColors( - containerColor = Color.Transparent, - scrolledContainerColor = Color.Transparent - ), - navigationIcon = { - IconButton( - onClick = { - onBack() - }, - content = { - Icon( - Icons.AutoMirrored.Filled.ArrowBack, - context.getString(R.string.action_back), - modifier = Modifier.size(25.dp) - ) - } - ) - }, - scrollBehavior = scrollBehavior - ) - } - ) { paddingValues -> - // TODO: Add function here! - val d = paddingValues - } -} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutSettings.kt new file mode 100644 index 0000000..eefdd64 --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/AboutSettings.kt @@ -0,0 +1,188 @@ +package io.zoemeow.dutschedule.ui.view.settings + +import android.content.Context +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.BuildConfig +import io.zoemeow.dutschedule.GlobalVariables +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.ui.component.base.OptionItem + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun Activity_Settings_AboutApplication( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState, + onLinkClicked: ((String) -> Unit)? = null, + onBack: () -> Unit +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, + topBar = { + LargeTopAppBar( + title = { Text(context.getString(R.string.settings_about_title)) }, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), + navigationIcon = { + IconButton( + onClick = { + onBack() + }, + content = { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + context.getString(R.string.action_back), + modifier = Modifier.size(25.dp) + ) + } + ) + }, + scrollBehavior = scrollBehavior + ) + } + ) { paddingValues -> + // TODO: Add function here! + Column( + modifier = Modifier.padding(paddingValues), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier + .size(130.dp) + .clip(RoundedCornerShape(200.dp)), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_launcher_background), + contentDescription = "", + contentScale = ContentScale.Crop + ) + Image( + modifier = Modifier.size(192.dp), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_launcher_foreground), + contentDescription = "", + contentScale = ContentScale.Crop + ) + } + Text( + context.getString(R.string.app_name), + style = MaterialTheme.typography.headlineMedium + ) + Text( + context.getString( + R.string.settings_option_version_description, + BuildConfig.VERSION_NAME, + BuildConfig.VERSION_CODE + ), + style = MaterialTheme.typography.titleMedium + ) + Spacer(modifier = Modifier.size(15.dp)) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.google_fonts_device_reset_24), + "changelog", + modifier = Modifier.padding(end = 15.dp) + ) + }, + title = context.getString(R.string.settings_about_changelog), + description = context.getString(R.string.settings_about_changelog_description), + onClick = { + onLinkClicked?.let { it(GlobalVariables.LINK_REPOSITORY_CHANGELOG) } + } + ) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_baseline_info_24), + "license", + modifier = Modifier.padding(end = 15.dp) + ) + }, + title = context.getString(R.string.settings_about_license), + description = GlobalVariables.LICENSE_STRING, + onClick = { + onLinkClicked?.let { it(GlobalVariables.LINK_REPOSITORY_LICENSE) } + } + ) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_baseline_info_24), + "credit", + modifier = Modifier.padding(end = 15.dp) + ) + }, + title = context.getString(R.string.settings_about_credit), + description = context.getString(R.string.settings_about_credit_description), + onClick = { + onLinkClicked?.let { it(GlobalVariables.LINK_REPOSITORY_CREDITS) } + } + ) + OptionItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.github_mark_24), + "repository", + modifier = Modifier.padding(end = 15.dp) + ) + }, + title = context.getString(R.string.settings_about_github), + description = GlobalVariables.LINK_REPOSITORY, + onClick = { + onLinkClicked?.let { it(GlobalVariables.LINK_REPOSITORY) } + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt index 3d86a63..323f1d1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt @@ -24,7 +24,9 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf @@ -329,44 +331,10 @@ fun Activity_Settings( BuildConfig.VERSION_CODE ), onClick = { - onMessageReceived?.let { it(context.getString(R.string.feature_not_ready), true, null, null) } - /* TODO: Implement here: Check for updates */ - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - leadingIcon = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.google_fonts_device_reset_24), - "", - modifier = Modifier.padding(end = 15.dp) - ) - }, - title = context.getString(R.string.settings_option_changelog), - description = context.getString(R.string.settings_option_changelog_description), - onClick = { - context.openLink( - url = GlobalVariables.LINK_CHANGELOG, - customTab = mainViewModel.appSettings.value.openLinkInsideApp, - ) - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - leadingIcon = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.github_mark_24), - "repository", - modifier = Modifier.padding(end = 15.dp) - ) - }, - title = context.getString(R.string.settings_option_github), - description = GlobalVariables.LINK_REPOSITORY, - onClick = { - context.openLink( - url = GlobalVariables.LINK_REPOSITORY, - customTab = mainViewModel.appSettings.value.openLinkInsideApp, - ) + // onMessageReceived?.let { it(context.getString(R.string.feature_not_ready), true, null, null) } + val intent = Intent(context, SettingsActivity::class.java) + intent.action = SettingsActivity.INTENT_ABOUTACTIVITY + context.startActivity(intent) } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt index 6b17971..14aa667 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow @@ -57,6 +58,10 @@ fun String.capitalized(): String { .joinToString(separator = " ") { it.lowercase().replaceFirstChar(Char::uppercase) } } +fun MutableState.clear() { + this.value = "" +} + @Composable fun Modifier.endOfListReached( lazyListState: LazyListState, diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index ffee1a6..f7dd9cf 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -91,10 +91,7 @@ Các cài đặt thử nghiệm của chúng tôi trước khi đưa vào chính thức. Sử dụng nó một cách thận trọng. Về ứng dụng Phiên bản - Hiện tại: %1$s (%2$d)\nNhấp vào đây để kiểm tra cập nhật. - Nhật ký thay đổi - Nhấn để xem ghi chú phát hành của ứng dụng - GitHub (nhấp để mở liên kết) + %1$s (%2$d) Chủ đề ứng dụng Theo chủ đề hệ thống @@ -198,6 +195,14 @@ Đúng, xóa nó Xóa tất cả bộ lọc tin tức? Bạn có chắc chắn muốn xóa tất cả bộ lọc tin tức không?\n\nHành động này không thể hoàn tác. + + Về ứng dụng + Nhật ký thay đổi + Nhấn để xem ghi chú phát hành của ứng dụng + Giấy phép + Credit + Nhấp vào đây để xem các thành phần mà ứng dụng này đã sử dụng + GitHub (nhấp để mở liên kết) Thông báo Không có thông báo @@ -206,6 +211,7 @@ Đã xóa tất cả thông báo! Tin tức + Tìm kiếm tin tức Chung Lớp học phần @@ -296,9 +302,10 @@ Chưa thanh toán Thông tin tài khoản + Mở Không có thông tin nào được trả từ máy chủ!\nKiểm tra thông tin đăng nhập và kết nối internet của bạn, và thử lại. Về việc thay đổi thông tin - Nếu bạn muốn thay đổi thông tin nào đó, bạn cần thay đổi nó trong trang web của trường. + Nếu bạn muốn thay đổi thông tin nào đó, bạn cần đăng nhập trong trang web của trường để thay đổi. Tên Ngày sinh Nơi sinh @@ -349,6 +356,19 @@ Số tín chỉ Công thức điểm Điểm (T10 - T4 - Bằng chữ) + + Liên kết liên quan + Liên kết liên quan của DUT + Tìm kiếm + Về liên kết liên quan của DUT + Trang này sẽ hiển thị liên kết được sử dụng nhiều nhất, vì vậy một số liên kết sẽ không xuất hiện tại đây. Bạn có thể điều hướng đến trang chính thức của DUT để biết thêm thông tin. + Tìm kiếm liên kết + + DutSchedule - Tin tức + DutSchedule - Tài khoản + DutSchedule - Cài đặt + DutSchedule - Linh tinh + Cập nhật tin tức trong nền (không rõ) (không có điểm) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f39bc99..f21f2f8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -91,10 +91,7 @@ Our current experiment settings before public. Use this at your own risk. About Version - Current: %1$s (%2$d)\nClick here to check for update. - Changelog - Tap to view app changelog - GitHub (click to open link) + %1$s (%2$d) App theme Follow device theme @@ -198,6 +195,14 @@ Yes, delete it Delete all subject filters? Are you sure you want to delete all subject filter?\n\nThis action is undone? + + About + Changelog + Tap to view app changelog + License + Credits + Click here to view what libraries has been used in this application + GitHub (click to open link) Notifications No notifications @@ -206,6 +211,7 @@ Successfully cleared all notifications! News + Search news Global Subject @@ -296,9 +302,10 @@ Not completed yet Account Information + Open No any information returned from server!\nCheck your login and internet connection, and try again. About editing information - If you want to edit any information below, you need do it in DUT Information System web. + If you want to edit any information, you need to login in DUT Information System web in order to change it. Name Date of birth Place of birth @@ -349,6 +356,19 @@ Credit(s) Point formula Point (T10 - T4 - By point char) + + Related links + DUT related links + Search + About DUT related links + This page will show most used links, so some links could be missed here. You can navigate to official DUT page for more information. + Search a link + + DutSchedule - News + DutSchedule - Accounts + DutSchedule - Settings + DutSchedule - Miscellaneous + News background update service (unknown) (no score) From d81a2e347ead87a76dccb36a135c227077a3b157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:33:15 +0700 Subject: [PATCH 04/12] 2.0-draft19 (1574) - Update Project - Experiment about NewsBackgroundUpdateService. - Optimize Save method. - Localize in Overview widgets. --- app/build.gradle | 2 +- .../dutschedule/activity/MainActivity.kt | 51 ++-- .../dutschedule/activity/SettingsActivity.kt | 47 ++-- .../dutschedule/model/ProcessVariable.kt | 18 +- .../model/account/DUTAccountInstance.kt | 31 +-- .../dutschedule/model/news/DUTNewsInstance.kt | 43 ++-- .../dutschedule/service/BaseService.kt | 4 + .../service/NewsBackgroundUpdateService.kt | 243 ++++++++++++++---- .../component/main/DateAndTimeSummaryItem.kt | 20 +- .../component/main/LessonsTodaySummaryItem.kt | 99 ++++--- .../component/main/SchoolNewsSummaryItem.kt | 15 +- .../ui/component/main/SummaryItem.kt | 2 +- .../ui/view/account/SubjectInformation.kt | 2 +- .../ui/view/main/MainViewDashboard.kt | 20 +- .../ui/view/main/MainViewDashboardView.kt | 6 +- .../ui/view/main/MainViewTabView.kt | 74 +++--- .../ui/view/settings/ExperimentSettings.kt | 26 +- .../ui/view/settings/LanguageSettings.kt | 4 + .../dutschedule/ui/view/settings/MainView.kt | 12 +- .../dutschedule/utils/FunctionExtension.kt | 63 ++--- .../dutschedule/viewmodel/MainViewModel.kt | 157 ++++++----- app/src/main/res/values-vi/strings.xml | 15 ++ app/src/main/res/values/strings.xml | 17 +- 23 files changed, 543 insertions(+), 428 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 96822cf..1c18f9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 34 - versionCode 1551 + versionCode 1574 versionName "2.0-draft19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt index f2a314e..cf20cf9 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/MainActivity.kt @@ -3,17 +3,14 @@ package io.zoemeow.dutschedule.activity import android.content.Context import android.content.Intent import android.util.Log -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import dagger.hilt.android.AndroidEntryPoint import io.zoemeow.dutschedule.model.AppearanceState -import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.service.BaseService import io.zoemeow.dutschedule.service.NewsBackgroundUpdateService +import io.zoemeow.dutschedule.ui.view.main.Activity_MainView_MainViewTabView import io.zoemeow.dutschedule.ui.view.main.MainViewDashboard -import io.zoemeow.dutschedule.ui.view.main.MainViewTabbed -import io.zoemeow.dutschedule.utils.BackgroundImageUtil import io.zoemeow.dutschedule.utils.NotificationsUtil @AndroidEntryPoint @@ -29,34 +26,6 @@ class MainActivity : BaseActivity() { NotificationsUtil.initializeNotificationChannel(this) } - // When active - val pickMedia = - registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - // Callback is invoked after the user selects a media item or closes the photo picker. - if (uri != null) { - Log.d("PhotoPicker", "Selected URI: $uri") - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - backgroundImage = BackgroundImageOption.None - ) - getMainViewModel().saveSettings( - onCompleted = { - BackgroundImageUtil.saveImageToAppData(this, uri) - Log.d("PhotoPicker", "Copied!") - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - backgroundImage = BackgroundImageOption.PickFileFromMedia - ) - getMainViewModel().saveSettings( - onCompleted = { - Log.d("PhotoPicker", "Copied!") - } - ) - } - ) - } else { - Log.d("PhotoPicker", "No media selected") - } - } - @Composable override fun OnMainView( context: Context, @@ -84,10 +53,22 @@ class MainActivity : BaseActivity() { } ) } else { - MainViewTabbed( + Activity_MainView_MainViewTabView( context = context, + mainViewModel = getMainViewModel(), snackBarHostState = snackBarHostState, - appearanceState = appearanceState + appearanceState = appearanceState, + onMessageReceived = { msg, forceDismissBefore, actionText, action -> + showSnackBar( + text = msg, + clearPrevious = forceDismissBefore, + actionText = actionText, + action = action + ) + }, + onMessageClear = { + clearSnackBar() + } ) } } @@ -105,7 +86,7 @@ class MainActivity : BaseActivity() { BaseService.startService( context = this, intent = Intent(applicationContext, NewsBackgroundUpdateService::class.java).also { - it.action = "news.service.action.fetchallpage1background.skipfirst" + it.action = "news.service.action.fetchallpage1background" } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt index 66131ee..d4f1e9f 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/SettingsActivity.kt @@ -8,7 +8,6 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import dagger.hilt.android.AndroidEntryPoint -import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.BackgroundImageOption @@ -19,6 +18,7 @@ import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ExperimentSetti import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_NewsNotificationSettings import io.zoemeow.dutschedule.ui.view.settings.Activity_Settings_ParseNewsSubjectNotification import io.zoemeow.dutschedule.utils.BackgroundImageUtil +import io.zoemeow.dutschedule.utils.NotificationsUtil import io.zoemeow.dutschedule.utils.openLink @AndroidEntryPoint @@ -34,6 +34,11 @@ class SettingsActivity : BaseActivity() { @Composable override fun OnPreloadOnce() { } + override fun onResume() { + super.onResume() + NotificationsUtil.initializeNotificationChannel(this) + } + // When active private val pickMedia = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> @@ -43,18 +48,17 @@ class SettingsActivity : BaseActivity() { getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( backgroundImage = BackgroundImageOption.None ) - getMainViewModel().saveSettings( + Log.d("PhotoPicker", "Switched to None to avoid cache!") + BackgroundImageUtil.saveImageToAppData(this, uri) + Log.d("PhotoPicker", "Copied image to app data!") + getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( + backgroundImage = BackgroundImageOption.PickFileFromMedia + ) + Log.d("PhotoPicker", "Switched to PickFileFromMedia!") + getMainViewModel().saveApplicationSettings( + saveUserSettings = true, onCompleted = { - BackgroundImageUtil.saveImageToAppData(this, uri) - Log.d("PhotoPicker", "Copied!") - getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( - backgroundImage = BackgroundImageOption.PickFileFromMedia - ) - getMainViewModel().saveSettings( - onCompleted = { - Log.d("PhotoPicker", "Copied!") - } - ) + Log.d("PhotoPicker", "Done saving settings!") } ) } else { @@ -79,7 +83,7 @@ class SettingsActivity : BaseActivity() { getMainViewModel().appSettings.value = getMainViewModel().appSettings.value.clone( newsBackgroundParseNewsSubject = !getMainViewModel().appSettings.value.newsBackgroundParseNewsSubject ) - getMainViewModel().saveSettings() + getMainViewModel().saveApplicationSettings(saveUserSettings = true) }, onBack = { setResult(RESULT_CANCELED) @@ -114,6 +118,9 @@ class SettingsActivity : BaseActivity() { context = context, snackBarHostState = snackBarHostState, appearanceState = appearanceState, + onNotificationRegister = { + NotificationsUtil.initializeNotificationChannel(this) + }, onBack = { setResult(RESULT_CANCELED) finish() @@ -139,7 +146,7 @@ class SettingsActivity : BaseActivity() { fetchNewsBackgroundDuration = duration ) getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = context.getString( R.string.settings_newsnotify_fetchnewsinbackground_enabled, @@ -164,7 +171,7 @@ class SettingsActivity : BaseActivity() { fetchNewsBackgroundDuration = 0 ) getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground_disabled), clearPrevious = true @@ -183,7 +190,7 @@ class SettingsActivity : BaseActivity() { newsBackgroundGlobalEnabled = enabled ) getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = when (enabled) { true -> context.getString(R.string.settings_newsnotify_newsglobal_enabled) @@ -206,7 +213,7 @@ class SettingsActivity : BaseActivity() { newsBackgroundSubjectEnabled = code ) getMainViewModel().appSettings.value = dataTemp - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) @Suppress("KotlinConstantConditions") showSnackBar( text = when (code) { @@ -226,7 +233,7 @@ class SettingsActivity : BaseActivity() { // Add a filter try { getMainViewModel().appSettings.value.newsBackgroundFilterList.add(subjectCode) - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = context.getString( R.string.settings_newsnotify_newsfilter_notify_add, @@ -244,7 +251,7 @@ class SettingsActivity : BaseActivity() { try { val data = subjectCode.copy() getMainViewModel().appSettings.value.newsBackgroundFilterList.remove(subjectCode) - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = context.getString( R.string.settings_newsnotify_newsfilter_notify_delete, @@ -261,7 +268,7 @@ class SettingsActivity : BaseActivity() { // Delete all filters try { getMainViewModel().appSettings.value.newsBackgroundFilterList.clear() - getMainViewModel().saveSettings(saveSettingsOnly = true) + getMainViewModel().saveApplicationSettings(saveUserSettings = true) showSnackBar( text = context.getString(R.string.settings_newsnotify_newsfilter_notify_deleteall), clearPrevious = true diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/ProcessVariable.kt b/app/src/main/java/io/zoemeow/dutschedule/model/ProcessVariable.kt index 4b60e98..516d037 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/ProcessVariable.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/ProcessVariable.kt @@ -5,10 +5,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import io.zoemeow.dutschedule.GlobalVariables -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import io.zoemeow.dutschedule.utils.launchOnScope data class ProcessVariable( val processState: MutableState = mutableStateOf(ProcessState.NotRunYet), @@ -37,19 +34,6 @@ data class ProcessVariable( } } - private fun launchOnScope( - script: () -> Unit, - invokeOnCompleted: ((Throwable?) -> Unit)? = null - ) { - CoroutineScope(Dispatchers.Main).launch { - withContext(Dispatchers.IO) { - script() - } - }.invokeOnCompletion { thr -> - invokeOnCompleted?.let { it(thr) } - } - } - fun refreshData( args: Map? = null, force: Boolean = false, diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt b/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt index 05d90ed..8c8fd6c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt @@ -11,7 +11,7 @@ import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.VariableListState import io.zoemeow.dutschedule.model.VariableState import io.zoemeow.dutschedule.repository.DutRequestRepository -import kotlinx.coroutines.CoroutineExceptionHandler +import io.zoemeow.dutschedule.utils.launchOnScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -45,23 +45,6 @@ class DUTAccountInstance( val accountInformation: VariableState = VariableState(data = mutableStateOf(null)) val accountTrainingStatus: VariableState = VariableState(data = mutableStateOf(null)) - private fun launchOnScope( - script: () -> Unit, - onCompleted: ((Throwable?) -> Unit)? = null - ) { - val handler = CoroutineExceptionHandler { _, throwable -> - onCompleted?.let { it(throwable) } - } - - CoroutineScope(Dispatchers.Main).launch(handler) { - withContext(Dispatchers.IO) { - script() - } - }.invokeOnCompletion { thr -> - onCompleted?.let { it(thr) } - } - } - private fun checkVariable(): Boolean { return when { this.accountSession.data.value == null -> false @@ -184,7 +167,7 @@ class DUTAccountInstance( throw Exception() } }, - onCompleted = { + invokeOnCompleted = { // TODO: Throwable here Log.d("login", "done login") it?.printStackTrace() @@ -258,7 +241,7 @@ class DUTAccountInstance( } } }, - onCompleted = { throwable -> + invokeOnCompleted = { throwable -> // Reset to NotRunYet (not logged in before) accountSession.processState.value = ProcessState.NotRunYet onEventSent?.let { it(1) } @@ -303,7 +286,7 @@ class DUTAccountInstance( subjectSchedule.data.addAll(data) } }, - onCompleted = { + invokeOnCompleted = { it?.printStackTrace() subjectSchedule.processState.value = when { (it != null) -> ProcessState.Failed @@ -351,7 +334,7 @@ class DUTAccountInstance( subjectFee.data.addAll(data) } }, - onCompleted = { + invokeOnCompleted = { it?.printStackTrace() subjectFee.processState.value = when { (it != null) -> ProcessState.Failed @@ -392,7 +375,7 @@ class DUTAccountInstance( accountInformation.data.value = data } }, - onCompleted = { + invokeOnCompleted = { it?.printStackTrace() accountInformation.processState.value = when { (it != null) -> ProcessState.Failed @@ -433,7 +416,7 @@ class DUTAccountInstance( accountTrainingStatus.data.value = data } }, - onCompleted = { + invokeOnCompleted = { it?.printStackTrace() accountTrainingStatus.processState.value = when { (it != null) -> ProcessState.Failed diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt b/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt index 3a50467..b587c50 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt @@ -3,14 +3,12 @@ package io.zoemeow.dutschedule.model.news import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.VariableListState import io.zoemeow.dutschedule.repository.DutRequestRepository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import io.zoemeow.dutschedule.utils.launchOnScope /** * @param onEventSent Event when done: - * 1: Done + * 1: Done global news + * 2: Done subject news */ class DUTNewsInstance( private val dutRequestRepository: DutRequestRepository, @@ -24,7 +22,7 @@ class DUTNewsInstance( parameters = mutableMapOf("nextPage" to "1") ) - fun loadNewsCache( + fun importNewsCache( globalNewsList: List? = null, globalNewsIndex: Int? = null, globalNewsLastRequest: Long? = null, @@ -54,34 +52,26 @@ class DUTNewsInstance( onDataExported: ( List, Int, + Long, List, - Int + Int, + Long ) -> Unit ) { onDataExported( newsGlobal.data, newsGlobal.parameters["nextPage"]?.toIntOrNull() ?: 1, + newsGlobal.lastRequest.longValue, newsSubject.data, newsSubject.parameters["nextPage"]?.toIntOrNull() ?: 1, + newsSubject.lastRequest.longValue ) } - private fun launchOnScope( - script: () -> Unit, - invokeOnCompleted: ((Throwable?) -> Unit)? = null - ) { - CoroutineScope(Dispatchers.Main).launch { - withContext(Dispatchers.IO) { - script() - } - }.invokeOnCompletion { thr -> - invokeOnCompleted?.let { it(thr) } - } - } - fun fetchGlobalNews( fetchType: NewsFetchType = NewsFetchType.NextPage, - forceRequest: Boolean = true + forceRequest: Boolean = true, + onDone: ((List) -> Unit)? = null ) { if (!newsGlobal.isSuccessfulRequestExpired() && !forceRequest) { return @@ -91,6 +81,7 @@ class DUTNewsInstance( } newsGlobal.processState.value = ProcessState.Running + val latestNews = arrayListOf() launchOnScope( script = { // Get news from internet @@ -110,7 +101,6 @@ class DUTNewsInstance( // - Filter latest news into a variable // - Remove duplicated news // - Update news from server - val latestNews = arrayListOf() newsFromInternet.forEach { newsTargetItem -> val anyMatch = newsGlobal.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date @@ -166,6 +156,7 @@ class DUTNewsInstance( } }, invokeOnCompleted = { + onDone?.let { it(latestNews) } newsGlobal.lastRequest.longValue = System.currentTimeMillis() newsGlobal.processState.value = when { it == null -> ProcessState.Successful @@ -178,7 +169,8 @@ class DUTNewsInstance( fun fetchSubjectNews( fetchType: NewsFetchType = NewsFetchType.NextPage, - forceRequest: Boolean = true + forceRequest: Boolean = true, + onDone: ((List) -> Unit)? = null ) { if (!newsSubject.isSuccessfulRequestExpired() && !forceRequest) { return @@ -188,6 +180,7 @@ class DUTNewsInstance( } newsSubject.processState.value = ProcessState.Running + val latestNews = arrayListOf() launchOnScope( script = { // Get news from internet @@ -207,7 +200,6 @@ class DUTNewsInstance( // - Filter latest news into a variable // - Remove duplicated news // - Update news from server - val latestNews = arrayListOf() newsFromInternet.forEach { newsTargetItem -> val anyMatch = newsSubject.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date @@ -263,12 +255,13 @@ class DUTNewsInstance( } }, invokeOnCompleted = { + onDone?.let { it(latestNews) } newsSubject.lastRequest.longValue = System.currentTimeMillis() newsSubject.processState.value = when { it == null -> ProcessState.Successful else -> ProcessState.Failed } - onEventSent?.let { it(1) } + onEventSent?.let { it(2) } } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/service/BaseService.kt b/app/src/main/java/io/zoemeow/dutschedule/service/BaseService.kt index 2ccfac0..62954b5 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/service/BaseService.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/service/BaseService.kt @@ -74,6 +74,10 @@ abstract class BaseService( abstract fun onInitialize() + /* + * This will self-destroyed after ran over 3 minutes, so keep in mind of done process + * before 3 minutes. + */ abstract fun doWorkBackground(intent: Intent?) abstract fun onCompleted(result: ProcessState) diff --git a/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt b/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt index e0a6e60..52c231d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt @@ -36,7 +36,6 @@ class NewsBackgroundUpdateService : BaseService( private lateinit var file: FileModuleRepository private lateinit var dutRequestRepository: DutRequestRepository private lateinit var settings: AppSettings - private lateinit var newsInstance: DUTNewsInstance override fun onInitialize() { file = FileModuleRepository(this) @@ -68,56 +67,20 @@ class NewsBackgroundUpdateService : BaseService( // val nofityType = intent?.getIntExtra("news.service.variable.notifytype", 0) ?: 0 when (intent?.action) { - "news.service.action.fetchglobal" -> { - fetchNewsGlobal( - fetchType = when (fetchType) { - 0 -> NewsFetchType.NextPage - 1 -> NewsFetchType.FirstPage - 2 -> NewsFetchType.ClearAndFirstPage - else -> NewsFetchType.NextPage - } - ) - } - "news.service.action.fetchsubject" -> { - fetchNewsSubject( - fetchType = when (fetchType) { - 0 -> NewsFetchType.NextPage - 1 -> NewsFetchType.FirstPage - 2 -> NewsFetchType.ClearAndFirstPage - else -> NewsFetchType.NextPage - } - ) - } - "news.service.action.fetchall" -> { - fetchNewsGlobal( - fetchType = when (fetchType) { - 0 -> NewsFetchType.NextPage - 1 -> NewsFetchType.FirstPage - 2 -> NewsFetchType.ClearAndFirstPage - else -> NewsFetchType.NextPage - } - ) - fetchNewsSubject( - fetchType = when (fetchType) { - 0 -> NewsFetchType.NextPage - 1 -> NewsFetchType.FirstPage - 2 -> NewsFetchType.ClearAndFirstPage - else -> NewsFetchType.NextPage - } - ) - } "news.service.action.fetchallpage1background" -> { - fetchNewsGlobal( - fetchType = NewsFetchType.FirstPage - ) - fetchNewsSubject( - fetchType = NewsFetchType.FirstPage - ) - - // Schedule next run - if (schedule) { - scheduleNextRun() + fetchNews { + // Schedule next run + if (schedule) { + scheduleNextRun() + } + stopSelf() } +// fetchNewsGlobal( +// fetchType = NewsFetchType.FirstPage +// ) +// fetchNewsSubject( +// fetchType = NewsFetchType.FirstPage +// ) } "news.service.action.fetchallpage1background.skipfirst" -> { // Do nothing @@ -125,12 +88,191 @@ class NewsBackgroundUpdateService : BaseService( // Schedule next run if (schedule) { scheduleNextRun() + stopSelf() } } else -> {} } } + private fun fetchNews(onDone: (() -> Unit)? = null) { + var newsGlobalDone = false + var newsSubjectDone = false + var notifyGlobalDone = false + var notifySubjectDone = false + + fun returnToMain() { + if (newsGlobalDone && newsSubjectDone && notifyGlobalDone && notifySubjectDone) { + // TODO: Return to main here + onDone?.let { it() } + } + } + + // If no notification permission, news notification must be aborted to avoid exception + val notificationPermission = PermissionsActivity.checkPermissionNotification(this).isGranted + + val dutNewsInstance = DUTNewsInstance( + dutRequestRepository = dutRequestRepository, + onEventSent = { when (it) { + 1 -> { + newsGlobalDone = true + returnToMain() + } + 2 -> { + newsSubjectDone = true + returnToMain() + } + } } + ) + + // Load news cache + file.getCacheNewsGlobal { newsList, page, lastRequest -> + dutNewsInstance.importNewsCache( + globalNewsList = newsList, + globalNewsIndex = page, + globalNewsLastRequest = lastRequest + ) + } + file.getCacheNewsSubject { newsList, page, lastRequest -> + dutNewsInstance.importNewsCache( + subjectNewsList = newsList, + subjectNewsIndex = page, + subjectNewsLastRequest = lastRequest + ) + } + + // Fetch news and direct notify here! + if (dutNewsInstance.newsGlobal.lastRequest.longValue + (settings.newsBackgroundDuration * 60 * 1000) > System.currentTimeMillis()) { + // throw Exception("Request too fast. Try again later.") + // TODO: Throw exception here + Log.d("NewsBackgroundService", "Request too fast in news global. Try again later.") + newsGlobalDone = true + notifyGlobalDone = true + returnToMain() + } else { + dutNewsInstance.fetchGlobalNews( + fetchType = NewsFetchType.FirstPage, + forceRequest = true, + onDone = { newsList -> + Log.d("NewsBackgroundService", "News global count: ${newsList.size}") + + // If we are not have news notifications permission, stop here + if (!notificationPermission) { + notifyGlobalDone = true + returnToMain() + return@fetchGlobalNews + } + + // If user turned off news global notifications, stop here + if (!settings.newsBackgroundGlobalEnabled) { + notifyGlobalDone = true + returnToMain() + return@fetchGlobalNews + } + + try { + newsList.forEach { + notifyNewsGlobal(this, it) + } + Log.d("NewsBackgroundService", "Done executing function in news global.") + } catch (ex: Exception) { + Log.w("NewsBackgroundService", "An error was occurred when executing function in news global.") + ex.printStackTrace() + } + + file.saveCacheNewsGlobal( + newsList = dutNewsInstance.newsGlobal.data, + newsNextPage = (dutNewsInstance.newsGlobal.parameters["nextPage"] ?: "0").toInt(), + lastRequest = dutNewsInstance.newsGlobal.lastRequest.longValue + ) + notifyGlobalDone = true + returnToMain() + } + ) + } + + if (dutNewsInstance.newsSubject.lastRequest.longValue + (settings.newsBackgroundDuration * 60 * 1000) > System.currentTimeMillis()) { + // throw Exception("Request too fast. Try again later.") + // TODO: Throw exception here + Log.d("NewsBackgroundService", "Request too fast in news subject. Try again later.") + newsSubjectDone = true + notifySubjectDone = true + returnToMain() + } else { + dutNewsInstance.fetchSubjectNews( + fetchType = NewsFetchType.FirstPage, + forceRequest = true, + onDone = { newsList -> + Log.d("NewsBackgroundService", "News subject count: ${newsList.size}") + + // If we are not have news notifications permission, stop here + if (!notificationPermission) { + notifySubjectDone = true + returnToMain() + return@fetchSubjectNews + } + + // If user turned off news subject notifications, stop here + if (settings.newsBackgroundSubjectEnabled == -1) { + notifySubjectDone = true + returnToMain() + return@fetchSubjectNews + } + + try { + newsList.forEach { newsItem -> + Log.d("NewsBackgroundService", "News subject index: ${newsList.indexOf(newsItem)}") + + // Default value is false. + var notifyRequired = false + // If enabled news filter, do following. + + // settings.newsBackgroundSubjectEnabled == 0 -> All news enabled + if (settings.newsBackgroundSubjectEnabled == 0) { + notifyRequired = true + } + // TODO: settings.newsBackgroundSubjectEnabled == 1 action + // settings.newsBackgroundSubjectEnabled == 2 + else if (settings.newsBackgroundFilterList.any { source -> + newsItem.affectedClass.any { targetGroup -> + targetGroup.codeList.any { target -> + source.isEquals( + SubjectCode( + target.studentYearId, + target.classId, + targetGroup.subjectName + ) + ) + } + } + } + ) notifyRequired = true + + // TODO: If no notify/notify settings is off, continue with return@forEach. + // notifyRequired and notify variable + + if (notifyRequired) { + notifyNewsSubject(this, newsItem) + } + } + Log.d("NewsBackgroundService", "Done executing function in news subject.") + } catch (ex: Exception) { + Log.w("NewsBackgroundService", "An error was occurred when executing function in news subject.") + ex.printStackTrace() + } + + file.saveCacheNewsSubject( + newsList = dutNewsInstance.newsSubject.data, + newsNextPage = (dutNewsInstance.newsSubject.parameters["nextPage"] ?: "0").toInt(), + lastRequest = dutNewsInstance.newsSubject.lastRequest.longValue + ) + notifySubjectDone = true + returnToMain() + } + ) + } + } + private fun fetchNewsGlobal( fetchType: NewsFetchType = NewsFetchType.NextPage ) { @@ -581,7 +723,8 @@ class NewsBackgroundUpdateService : BaseService( } override fun onCompleted(result: ProcessState) { - stopSelf() + // TODO: We need another process result here! + // stopSelf() } override fun onDestroying() { } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt index 04eb88b..f4c4707 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt @@ -1,5 +1,6 @@ package io.zoemeow.dutschedule.ui.component.main +import android.content.Context import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -16,12 +17,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem +import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.CustomClock import io.zoemeow.dutschedule.utils.CustomDateUtil import kotlinx.coroutines.delay @Composable fun DateAndTimeSummaryItem( + context: Context, padding: PaddingValues = PaddingValues(), isLoading: Boolean = false, currentSchoolWeek: DutSchoolYearItem? = null, @@ -31,10 +34,9 @@ fun DateAndTimeSummaryItem( SummaryItem( padding = padding, - title = "Date & time today", + title = context.getString(R.string.main_dashboard_widget_datetime_title), isLoading = false, opacity = opacity, - clicked = { }, content = { Column( modifier = Modifier.fillMaxWidth(), @@ -58,10 +60,10 @@ fun DateAndTimeSummaryItem( } false -> { Text( - text = String.format( - "School year: %s - Week: %s\nCurrent lesson: %s", - currentSchoolWeek?.schoolYear ?: "(unknown)", - currentSchoolWeek?.week?.toString() ?: "(unknown)", + text = context.getString( + R.string.main_dashboard_widget_datetime_schoolstat, + currentSchoolWeek?.schoolYear ?: context.getString(R.string.data_unknown), + currentSchoolWeek?.week?.toString() ?: context.getString(R.string.data_unknown), CustomClock.getCurrent().toDUTLesson2().name ), style = MaterialTheme.typography.bodyMedium, @@ -78,9 +80,9 @@ fun DateAndTimeSummaryItem( LaunchedEffect(Unit) { while (true) { - String.format( - "Date and time: %s\n(based on your current region)\n", - CustomDateUtil.getCurrentDateAndTimeToString("dd/MM/yyyy HH:mm:ss"), + context.getString( + R.string.main_dashboard_widget_datetime_datetimestat, + CustomDateUtil.getCurrentDateAndTimeToString("dd/MM/yyyy HH:mm:ss") ).also { dateTimeString.value = it } delay(1000) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt index 18a6390..c0c4e48 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt @@ -1,5 +1,6 @@ package io.zoemeow.dutschedule.ui.component.main +import android.content.Context import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme @@ -8,10 +9,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem +import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.CustomClock +import java.util.Locale @Composable fun LessonTodaySummaryItem( + context: Context, hasLoggedIn: Boolean = false, isLoading: Boolean = false, affectedList: List = listOf(), @@ -19,55 +23,66 @@ fun LessonTodaySummaryItem( clicked: () -> Unit, opacity: Float = 1.0f ) { - fun affectedListStringBuilder(): String { - val result = arrayListOf() - val currentLesson = CustomClock.getCurrent().toDUTLesson2() - affectedList.forEach { item -> - val childResult = String.format( - "%s (%s)", - item.name, - item.subjectStudy.scheduleList.filter { it.lesson.end >= currentLesson.lesson }.joinToString( - separator = ", ", - transform = { String.format("%d-%d", it.lesson.start, it.lesson.end) } - ) - ) - result.add(childResult) - } - - return result.joinToString(separator = "\n") - } - - fun summaryStringBuilder(): String { - return if (!hasLoggedIn) { - "You haven't logged in! We can't fetch data for you!\nLog in to continue using this function." - } else if (affectedList.isEmpty()) { - "You have completed all lessons today. Have a rest!" - } else { - String.format( - "%s\n\n%s", - String.format( - "You have %d%s lesson%s today:", - affectedList.size, - when { - (CustomClock.getCurrent().toDUTLesson2().lesson in 1.0..14.0) -> " remaining" - else -> "" - }, - if (affectedList.size != 1) "s" else "" - ), - affectedListStringBuilder() - ) - } - } - SummaryItem( padding = padding, - title = "Your subjects today", + title = context.getString(R.string.main_dashboard_widget_lessontoday_title), clicked = clicked, isLoading = isLoading, opacity = opacity, content = { Text( - text = summaryStringBuilder(), + text = when { + !hasLoggedIn -> context.getString(R.string.main_dashboard_widget_msg_notloggedin) + affectedList.isEmpty() -> context.getString(R.string.main_dashboard_widget_lessontoday_completed) + else -> { + run { + val result = arrayListOf() + val currentLesson = CustomClock.getCurrent().toDUTLesson2() + affectedList.forEach { item -> + val childResult = String.format( + Locale.ROOT, + "%s (%s)", + item.name, + item.subjectStudy.scheduleList.filter { it.lesson.end >= currentLesson.lesson }.joinToString( + separator = ", ", + transform = { String.format(Locale.ROOT, "%d-%d", it.lesson.start, it.lesson.end) } + ) + ) + result.add(childResult) + } + + return@run result.joinToString(separator = "\n") + }.also { affectedString -> + if (CustomClock.getCurrent().toDUTLesson2().lesson in 1.0..14.0) { + if (affectedList.size == 1) { + context.getString( + R.string.main_dashboard_widget_lessontoday_availabletoday_1, + affectedString + ) + } else { + context.getString( + R.string.main_dashboard_widget_lessontoday_availabletoday_other, + affectedList.size, + affectedString + ) + } + } else { + if (affectedList.size == 1) { + context.getString( + R.string.main_dashboard_widget_lessontoday_availablepending_1, + affectedString + ) + } else { + context.getString( + R.string.main_dashboard_widget_lessontoday_availablepending_other, + affectedList.size, + affectedString + ) + } + } + } + } + }, style = MaterialTheme.typography.bodyMedium, modifier = Modifier .padding(horizontal = 15.dp) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SchoolNewsSummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SchoolNewsSummaryItem.kt index 722532c..c0ef94a 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SchoolNewsSummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SchoolNewsSummaryItem.kt @@ -1,5 +1,6 @@ package io.zoemeow.dutschedule.ui.component.main +import android.content.Context import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme @@ -7,27 +8,29 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.R @Composable fun SchoolNewsSummaryItem( + context: Context, padding: PaddingValues, clicked: () -> Unit, newsToday: Int = 0, newsThisWeek: Int = 0, isLoading: Boolean = false, - opacity: Float = 1.0f + opacity: Float = 1f ) { SummaryItem( padding = padding, - title = "School news", + title = context.getString(R.string.main_dashboard_widget_news_title), isLoading = isLoading, opacity = opacity, content = { Text( - text = String.format( - "Tap here to open news.\nNews global: %s new today, %s new last 7 days.", - if (newsToday == 0) "No" else newsToday.toString(), - if (newsThisWeek == 0) "No" else newsThisWeek.toString() + text = context.getString( + R.string.main_dashboard_widget_news_status, + newsToday.toString(), + newsThisWeek.toString() ), style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(horizontal = 15.dp).padding(bottom = 10.dp), diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt index 8534d37..2852ac7 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/SummaryItem.kt @@ -16,7 +16,7 @@ fun SummaryItem( title: String, content: @Composable () -> Unit, isLoading: Boolean = false, - clicked: () -> Unit, + clicked: (() -> Unit)? = null, padding: PaddingValues = PaddingValues(10.dp), opacity: Float = 1.0f ) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt index a95ed23..f823d02 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt @@ -172,7 +172,7 @@ fun Activity_Account_SubjectInformation( it.add(item) } ) - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) onMessageReceived(context.getString( R.string.account_subjectinfo_filter_added, item diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index 69917f5..ff505c8 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -209,15 +209,16 @@ fun Activity_MainView_Dashboard_Body( .verticalScroll(rememberScrollState()) ) { DateAndTimeSummaryItem( + context = context, padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), isLoading = mainViewModel.currentSchoolYearWeek.processState.value == ProcessState.Running, currentSchoolWeek = mainViewModel.currentSchoolYearWeek.data.value, opacity = appearanceState.componentOpacity ) - when (mainViewModel.accountSession.accountSession.processState.value) { - ProcessState.Successful, - ProcessState.Failed -> { + when (mainViewModel.accountSession.accountSession.data.value?.accountAuth?.isValidLogin()) { + true -> { LessonTodaySummaryItem( + context = context, padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, @@ -233,7 +234,8 @@ fun Activity_MainView_Dashboard_Body( affectedList = mainViewModel.accountSession.subjectSchedule.data.filter { subSch -> subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && subSch.subjectStudy.scheduleList.any { schItem -> - schItem.lesson.end >= CustomClock.getCurrent().toDUTLesson2().lesson + schItem.lesson.end >= CustomClock.getCurrent() + .toDUTLesson2().lesson } }.toList(), opacity = appearanceState.componentOpacity @@ -257,18 +259,18 @@ fun Activity_MainView_Dashboard_Body( opacity = appearanceState.componentOpacity ) } - ProcessState.NotRunYet -> { - // TODO: Toast for not logged in here + + else -> { SummaryItem( padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - title = "Login-only widgets", + title = context.getString(R.string.main_dashboard_widget_notloggedin_title), opacity = appearanceState.componentOpacity, clicked = { onLoginRequested?.let { it() } }, content = { Text( - text = "Lessons in today and affected lessons widget will available after logged in. To do so, click here to navigate to login page.", + text = context.getString(R.string.main_dashboard_widget_notloggedin_description), style = MaterialTheme.typography.bodyMedium, modifier = Modifier .padding(horizontal = 15.dp) @@ -277,9 +279,9 @@ fun Activity_MainView_Dashboard_Body( } ) } - else -> { } } SchoolNewsSummaryItem( + context = context, padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), newsToday = run { val today = LocalDateTime( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt index ce0390e..44c5b7c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboardView.kt @@ -242,13 +242,13 @@ fun MainActivity.MainViewDashboard( onClear = { item -> val item1 = item.clone() getMainViewModel().notificationHistory.remove(item) - getMainViewModel().saveSettings() + getMainViewModel().saveApplicationSettings(saveNotificationCache = true) showSnackBar( text = context.getString(R.string.notification_removed), actionText = context.getString(R.string.action_undo), action = { getMainViewModel().notificationHistory.add(item1) - getMainViewModel().saveSettings() + getMainViewModel().saveApplicationSettings(saveNotificationCache = true) } ) }, @@ -258,7 +258,7 @@ fun MainActivity.MainViewDashboard( actionText = context.getString(R.string.action_confirm), action = { getMainViewModel().notificationHistory.clear() - getMainViewModel().saveSettings() + getMainViewModel().saveApplicationSettings(saveNotificationCache = true) showSnackBar( text = context.getString(R.string.notification_removeall_removed), clearPrevious = true diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt index 9003fb1..15c6d3a 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt @@ -33,7 +33,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.MiscellaneousActivity -import io.zoemeow.dutschedule.activity.MainActivity import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.AppearanceState @@ -42,12 +41,16 @@ import io.zoemeow.dutschedule.model.settings.BackgroundImageOption import io.zoemeow.dutschedule.ui.view.account.Activity_Account import io.zoemeow.dutschedule.ui.view.news.Activity_News import io.zoemeow.dutschedule.utils.BackgroundImageUtil +import io.zoemeow.dutschedule.viewmodel.MainViewModel @Composable -fun MainActivity.MainViewTabbed( +fun Activity_MainView_MainViewTabView( context: Context, + mainViewModel: MainViewModel, snackBarHostState: SnackbarHostState, - appearanceState: AppearanceState + appearanceState: AppearanceState, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onMessageClear: () -> Unit ) { // Initialize for NavController for main activity val navController = rememberNavController() @@ -112,8 +115,8 @@ fun MainActivity.MainViewTabbed( Activity_MainView_Dashboard( context = context, appearanceState = appearanceState, - mainViewModel = getMainViewModel(), - notificationCount = getMainViewModel().notificationHistory.size, + mainViewModel = mainViewModel, + notificationCount = mainViewModel.notificationHistory.size, onNotificationPanelRequested = { isNotificationOpened.value = true }, @@ -150,7 +153,7 @@ fun MainActivity.MainViewTabbed( Activity_News( context = context, appearanceState = appearanceState, - mainViewModel = getMainViewModel(), + mainViewModel = mainViewModel, searchRequested = { val intent = Intent(context, NewsActivity::class.java) intent.action = NewsActivity.INTENT_SEARCHACTIVITY @@ -163,9 +166,9 @@ fun MainActivity.MainViewTabbed( Activity_Account( context = context, appearanceState = appearanceState, - mainViewModel = getMainViewModel(), + mainViewModel = mainViewModel, onMessageReceived = { text, clearPrevious, actionText, action -> - showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) + onMessageReceived(text, clearPrevious, actionText, action) } ) } @@ -188,17 +191,17 @@ fun MainActivity.MainViewTabbed( ) NotificationScaffold( context = context, - itemList = getMainViewModel().notificationHistory, + itemList = mainViewModel.notificationHistory, snackBarHostState = snackBarHostState, isVisible = isNotificationOpened.value, appearanceState = appearanceState, - backgroundImage = when (getMainViewModel().appSettings.value.backgroundImage) { + backgroundImage = when (mainViewModel.appSettings.value.backgroundImage) { BackgroundImageOption.None -> null BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) }, onDismiss = { - clearSnackBar() + onMessageClear() isNotificationOpened.value = false }, onClick = { item -> @@ -214,31 +217,32 @@ fun MainActivity.MainViewTabbed( }, onClear = { item -> val item1 = item.clone() - getMainViewModel().notificationHistory.remove(item) - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString(R.string.notification_removed), - actionText = context.getString(R.string.action_undo), - action = { - getMainViewModel().notificationHistory.add(item1) - getMainViewModel().saveSettings() - } - ) + mainViewModel.notificationHistory.remove(item) + mainViewModel.saveApplicationSettings(saveNotificationCache = true) + onMessageReceived( + context.getString(R.string.notification_removed), + true, + context.getString(R.string.action_undo) + ) { + mainViewModel.notificationHistory.add(item1) + mainViewModel.saveApplicationSettings(saveNotificationCache = true) + } }, onClearAll = { - showSnackBar( - text = context.getString(R.string.notification_removeall_confirm), - actionText = context.getString(R.string.action_confirm), - action = { - getMainViewModel().notificationHistory.clear() - getMainViewModel().saveSettings() - showSnackBar( - text = context.getString(R.string.notification_removeall_removed), - clearPrevious = true - ) - }, - clearPrevious = true - ) + onMessageReceived( + context.getString(R.string.notification_removeall_confirm), + true, + context.getString(R.string.action_confirm), + ) { + mainViewModel.notificationHistory.clear() + mainViewModel.saveApplicationSettings(saveNotificationCache = true) + onMessageReceived( + context.getString(R.string.notification_removeall_removed), + true, + null, + null + ) + } } ) BackHandler(isNotificationOpened.value) { @@ -246,4 +250,4 @@ fun MainActivity.MainViewTabbed( isNotificationOpened.value = false } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt index f992a3b..ddd3155 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt @@ -122,26 +122,11 @@ fun Activity_Settings_ExperimentSettings( content = { OptionItem( modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - title = context.getString(R.string.settings_experiment_option_bgopacity), + title = context.getString(R.string.settings_experiment_option_backgroundandcomponentopacity), description = String.format( Locale.ROOT, - "%2.0f%% %s", + "%2.0f%% - %2.0f%% %s", (mainViewModel.appSettings.value.backgroundImageOpacity * 100), - if (mainViewModel.appSettings.value.backgroundImage == BackgroundImageOption.None) { - "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" - } else "" - ), - onClick = { - onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) - /* TODO: Implement here: Background opacity */ - } - ) - OptionItem( - modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), - title = context.getString(R.string.settings_experiment_option_componentopacity), - description = String.format( - Locale.ROOT, - "%2.0f%% %s", (mainViewModel.appSettings.value.componentOpacity * 100), if (mainViewModel.appSettings.value.backgroundImage == BackgroundImageOption.None) { "(${context.getString(R.string.settings_experiment_option_required_enableimage)})" @@ -149,7 +134,7 @@ fun Activity_Settings_ExperimentSettings( ), onClick = { onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) - /* TODO: Implement here: Component opacity */ + /* TODO: Implement here: Background and Component opacity */ } ) // https://stackoverflow.com/questions/72932093/jetpack-compose-is-there-a-way-to-restart-whole-app-programmatically @@ -179,7 +164,8 @@ fun Activity_Settings_ExperimentSettings( mainViewModel.appSettings.value.clone( mainScreenDashboardView = !mainViewModel.appSettings.value.mainScreenDashboardView ) - mainViewModel.saveSettings( + mainViewModel.saveApplicationSettings( + saveUserSettings = true, onCompleted = { val packageManager: PackageManager = context.packageManager @@ -228,7 +214,7 @@ fun Activity_Settings_ExperimentSettings( mainViewModel.appSettings.value = mainViewModel.appSettings.value.clone( currentSchoolYear = it ) - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) dialogSchoolYear.value = false } ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt index ead7321..af6ae21 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/LanguageSettings.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.unit.sp import androidx.core.os.LocaleListCompat import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.utils.NotificationsUtil import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -51,6 +52,7 @@ fun Activity_Settings_AppLanguageSettings( context: Context, snackBarHostState: SnackbarHostState, appearanceState: AppearanceState, + onNotificationRegister: () -> Unit, onBack: () -> Unit ) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -94,6 +96,7 @@ fun Activity_Settings_AppLanguageSettings( val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(defaultLocale) AppCompatDelegate.setApplicationLocales(appLocale) } + onNotificationRegister() }, content = { Icon( @@ -137,6 +140,7 @@ fun Activity_Settings_AppLanguageSettings( val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(tag) AppCompatDelegate.setApplicationLocales(appLocale) } + onNotificationRegister() } ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt index 323f1d1..4ceda88 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt @@ -24,9 +24,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf @@ -38,7 +36,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp import io.zoemeow.dutschedule.BuildConfig -import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.PermissionsActivity import io.zoemeow.dutschedule.activity.SettingsActivity @@ -51,7 +48,6 @@ import io.zoemeow.dutschedule.ui.component.base.OptionSwitchItem import io.zoemeow.dutschedule.ui.component.settings.ContentRegion import io.zoemeow.dutschedule.ui.component.settings.DialogAppBackgroundSettings import io.zoemeow.dutschedule.ui.component.settings.DialogAppThemeSettings -import io.zoemeow.dutschedule.utils.openLink import io.zoemeow.dutschedule.viewmodel.MainViewModel import java.util.Locale @@ -198,7 +194,7 @@ fun Activity_Settings( mainViewModel.appSettings.value.clone( blackBackground = value ) - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) } ) OptionItem( @@ -286,7 +282,7 @@ fun Activity_Settings( mainViewModel.appSettings.value.clone( openLinkInsideApp = value ) - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) } ) OptionItem( @@ -354,7 +350,7 @@ fun Activity_Settings( themeMode = themeMode, dynamicColor = dynamicColor ) - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) } ) DialogAppBackgroundSettings( @@ -401,7 +397,7 @@ fun Activity_Settings( } dialogBackground.value = false - mainViewModel.saveSettings() + mainViewModel.saveApplicationSettings(saveUserSettings = true) } BackHandler( enabled = dialogAppTheme.value || dialogBackground.value, diff --git a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt index 14aa667..56d1430 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt @@ -5,30 +5,22 @@ import android.content.Intent import android.net.Uri import androidx.browser.customtabs.CustomTabColorSchemeParams import androidx.browser.customtabs.CustomTabsIntent -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState -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.MutableState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.math.BigInteger import java.security.MessageDigest import java.text.Normalizer -import java.util.Locale fun Context.openLink( url: String, @@ -92,35 +84,6 @@ fun Modifier.endOfListReached( return this } -@Composable -fun RowScope.TableCell( - modifier: Modifier = Modifier, - text: String, - backgroundColor: Color = MaterialTheme.colorScheme.surface, - contentAlign: Alignment = Alignment.Center, - textAlign: TextAlign = TextAlign.Start, - weight: Float -) { - Surface( - modifier = modifier.weight(weight), - border = BorderStroke(0.5.dp, MaterialTheme.colorScheme.inverseSurface), - color = backgroundColor, - content = { - Box( - modifier = Modifier.padding(1.dp), - contentAlignment = contentAlign, - content = { - Text( - text = text, - textAlign = textAlign, - modifier = Modifier.padding(8.dp), - ) - } - ) - } - ) -} - fun String.toNonAccent(): String { val temp = Normalizer.normalize(this, Normalizer.Form.NFD) return "\\p{InCombiningDiacriticalMarks}+".toRegex().replace(temp, "") @@ -147,4 +110,22 @@ fun getRandomString(length: Int): String { return (1..length) .map { allowedChars.random() } .joinToString("") +} + +fun launchOnScope( + script: () -> Unit, + invokeOnCompleted: ((Throwable?) -> Unit)? = null +) { + var exRoot: Throwable? = null + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + try { + script() + } catch (ex: Exception) { + exRoot = ex + } + } + }.invokeOnCompletion { + invokeOnCompleted?.let { it(exRoot) } + } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt index 90d9720..6bd3f4b 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt @@ -20,18 +20,13 @@ import io.zoemeow.dutschedule.model.news.NewsFetchType import io.zoemeow.dutschedule.model.settings.AppSettings import io.zoemeow.dutschedule.repository.DutRequestRepository import io.zoemeow.dutschedule.repository.FileModuleRepository -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import io.zoemeow.dutschedule.utils.launchOnScope import javax.inject.Inject @HiltViewModel class MainViewModel @Inject constructor( private val fileModuleRepository: FileModuleRepository, - private val dutRequestRepository: DutRequestRepository, + dutRequestRepository: DutRequestRepository, ) : ViewModel() { val appSettings: MutableState = mutableStateOf(AppSettings()) @@ -41,11 +36,11 @@ class MainViewModel @Inject constructor( when (eventId) { 1 -> { Log.d("app", "triggered saved login") - saveSettings() + saveApplicationSettings(saveAccountCache = true) } 2, 3, 4, 5 -> { // TODO: Save account cache here! - // saveSettings() + saveApplicationSettings(saveAccountCache = true) } } } @@ -55,9 +50,9 @@ class MainViewModel @Inject constructor( dutRequestRepository = dutRequestRepository, onEventSent = {eventId -> when (eventId) { - 1 -> { + 1, 2 -> { Log.d("app", "triggered saved news") - saveSettings() + saveApplicationSettings(saveNewsCache = true) } } } @@ -101,35 +96,47 @@ class MainViewModel @Inject constructor( val notificationHistory = mutableStateListOf() - private fun loadCacheNotification() { - launchOnScope( - script = { - notificationHistory.clear() - notificationHistory.addAll(fileModuleRepository.getNotificationHistory()) - } - ) - } - - /** * Save all current settings to file in storage. */ - fun saveSettings( - saveSettingsOnly: Boolean = false, + fun saveApplicationSettings( + saveNewsCache: Boolean = false, + saveAccountCache: Boolean = false, + saveNotificationCache: Boolean = false, + saveUserSettings: Boolean = false, onCompleted: (() -> Unit)? = null ) { launchOnScope( script = { - fileModuleRepository.saveAppSettings(appSettings.value) - fileModuleRepository.saveAccountSession(accountSession.getAccountSession() ?: AccountSession()) - - if (!saveSettingsOnly) { + if (saveNewsCache) { + newsInstance.exportNewsCache( + onDataExported = { newsGlobalItems, i, l, newsSubjectItems, i2, l2 -> + fileModuleRepository.saveCacheNewsGlobal( + newsList = newsGlobalItems, + newsNextPage = i, + lastRequest = l + ) + fileModuleRepository.saveCacheNewsSubject( + newsList = newsSubjectItems, + newsNextPage = i2, + lastRequest = l2 + ) + } + ) + } + if (saveAccountCache) { fileModuleRepository.saveAccountSubjectScheduleCache(ArrayList(accountSession.getSubjectScheduleCache())) - fileModuleRepository.saveNotificationHistory(ArrayList(notificationHistory.toList())) // Reload school year in Account accountSession.setSchoolYear(appSettings.value.currentSchoolYear) } + if (saveNotificationCache) { + fileModuleRepository.saveNotificationHistory(ArrayList(notificationHistory.toList())) + } + if (saveUserSettings) { + fileModuleRepository.saveAppSettings(appSettings.value) + fileModuleRepository.saveAccountSession(accountSession.getAccountSession() ?: AccountSession()) + } }, invokeOnCompleted = { onCompleted?.let { it() } } ) @@ -138,17 +145,30 @@ class MainViewModel @Inject constructor( /** * Load all cache if possible for offline reading. */ - private fun loadCache() { + private fun loadApplicationSettings( + onCompleted: (() -> Unit)? = null + ) { launchOnScope( script = { - // Get all news cache + // App settings + appSettings.value = fileModuleRepository.getAppSettings() + accountSession.setAccountSession(fileModuleRepository.getAccountSession()) + accountSession.setSchoolYear(schoolYearItem = appSettings.value.currentSchoolYear) + + // Notification cache + notificationHistory.clear() + notificationHistory.addAll(fileModuleRepository.getNotificationHistory()) + + // Load all cache if possible for offline reading. + // Global news cache fileModuleRepository.getCacheNewsGlobal { newsGlobalItems, i, lq -> - newsInstance.loadNewsCache(newsGlobalItems, i, lq, null, null, null) + newsInstance.importNewsCache(newsGlobalItems, i, lq, null, null, null) } + // Subject news cache fileModuleRepository.getCacheNewsSubject { newsSubjectItems, i, lq -> - newsInstance.loadNewsCache(null, null, null, newsSubjectItems, i, lq) + newsInstance.importNewsCache(null, null, null, newsSubjectItems, i, lq) } - + // Subject schedule cache fileModuleRepository.getAccountSubjectScheduleCache().also { accountSession.setSubjectScheduleCache(it) } @@ -164,56 +184,33 @@ class MainViewModel @Inject constructor( } catch (_: Exception) { } } } - } + }, + invokeOnCompleted = { onCompleted?.let { it() } } ) } - private fun launchOnScope( - script: () -> Unit, - invokeOnCompleted: ((Throwable?) -> Unit)? = null - ) { - CoroutineScope(Dispatchers.Main).launch { - withContext(Dispatchers.IO) { - script() - } - }.invokeOnCompletion { thr -> - invokeOnCompleted?.let { it(thr) } - } - } - - private val _runOnStartup = MutableStateFlow(false) - val runOnStartup = _runOnStartup.asStateFlow() - - private fun runOnStartup(invokeOnCompleted: (() -> Unit)? = null) { - if (_runOnStartup.value) - return - - appSettings.value = fileModuleRepository.getAppSettings() - accountSession.setAccountSession(fileModuleRepository.getAccountSession()) - accountSession.setSchoolYear(schoolYearItem = appSettings.value.currentSchoolYear) - - invokeOnCompleted?.let { it() } - } - + private val _runOnStartup = mutableStateOf(false) init { - runOnStartup( - invokeOnCompleted = { - loadCache() - refreshCurrentSchoolYearWeek() - loadCacheNotification() - accountSession.reLogin(force = true) - launchOnScope(script = { - newsInstance.fetchGlobalNews( - fetchType = NewsFetchType.FirstPage, - forceRequest = true - ) - newsInstance.fetchSubjectNews( - fetchType = NewsFetchType.FirstPage, - forceRequest = true - ) - }) - _runOnStartup.value = true - } - ) + if (!_runOnStartup.value) { + loadApplicationSettings( + onCompleted = { + accountSession.reLogin(force = true) + refreshCurrentSchoolYearWeek() + + launchOnScope(script = { + newsInstance.fetchGlobalNews( + fetchType = NewsFetchType.FirstPage, + forceRequest = true + ) + newsInstance.fetchSubjectNews( + fetchType = NewsFetchType.FirstPage, + forceRequest = true + ) + }) + } + ) + + _runOnStartup.value = true + } } } \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index f7dd9cf..5c9275b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -44,6 +44,20 @@ Tổng quan Chưa đăng nhập Đang tải… + Bạn chưa đăng nhập! Chúng tôi không thể tải dữ liệu cho bạn!\nĐể tiếp tục, hãy đăng nhập. + Một số widget đã ẩn + Widget tiết học hôm nay và tiết học bị ảnh hưởng sẽ có sẵn khi đăng nhập. Để tiếp tục, nhấp vào đây để điều hướng đến trang đăng nhập. + Ngày & giờ + Năm học: %1$s - Tuần: %2$s\nTiết học hiện tại: %3$s + Ngày và giờ: %s\n(dựa theo cài đặt vùng hiện tại của bạn)\n + Tiết học hôm nay của bạn + Bạn đã hoàn thành tất cả môn học hôm nay. Hãy nghỉ ngơi! + Bạn còn 1 môn học còn lại trong hôm nay:\n\n%1$s + Bạn còn %1$d môn học còn lại trong hôm nay:\n\n%2$s + Bạn có 1 môn học còn lại trong hôm nay:\n\n%1$s + Bạn có %1$d môn học còn lại trong hôm nay:\n\n%2$s + Tin tức + Nhấn vào đây để mở tin tức.\nTin tức chung: %1$s mới hôm nay, %2$s+ mới trong 7 ngày qua. Thông báo ứng dụng Kênh này sẽ gửi thông báo cập nhật quan trọng của ứng dụng cho bạn. @@ -123,6 +137,7 @@ (học kỳ hè) Hiển thị Bạn cần bật hình nền ứng dụng để tùy chọn có tác dụng + Độ mờ hình nền & thành phần điều khiển Độ mờ hình nền Độ mờ thành phần điều khiển Chế độ xem bảng tổng quát trên màn hình chính diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f21f2f8..19ec04b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -41,9 +41,23 @@ Today Yesterday - Dashboard + Overview Not logged in Fetching… + You haven\'t logged in! We can\'t fetch data for you!\nTo continue, please login. + Login-only widgets + Lessons in today and affected lessons widget will available after logged in. To do so, click here to navigate to login page. + Date & time + School year: %1$s - Week: %2$s\nCurrent lesson: %3$s + Date and time: %s\n(based on your current region settings)\n + Your subjects today + You have completed all lessons today. Have a rest! + You have 1 lesson remaining today:\n\n%1$s + You have %1$d lessons remaining today:\n\n%2$s + You have 1 lesson today:\n\n%1$s + You have %1$d lessons today:\n\n%2$s + News + Tap here to open news.\nGlobal news: %1$s new today, %2$s+ new last 7 days. App updates This will send you app recommend and important updates. @@ -123,6 +137,7 @@ (in summer) Appearance You need enable background image to take effect + Background & component opacity Background opacity Component opacity Main screen dashboard view From fe6dcdc43e665c8d3dfddab479f7b66a0cb6f232 Mon Sep 17 00:00:00 2001 From: ZoeMeow1027 <47247560+ZoeMeow1027@users.noreply.github.com> Date: Sun, 14 Jul 2024 10:38:14 +0700 Subject: [PATCH 05/12] Update Project - Gradle 8.9 - Target SDK 35 (Android 15) --- app/build.gradle | 8 ++++++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1c18f9d..3595436 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,12 +9,12 @@ plugins { android { namespace 'io.zoemeow.dutschedule' - compileSdk 34 + compileSdk 35 defaultConfig { applicationId "io.zoemeow.dutschedule" minSdk 21 - targetSdkVersion 34 + targetSdkVersion 35 versionCode 1574 versionName "2.0-draft19" @@ -136,3 +136,7 @@ dependencies { kapt { correctErrorTypes true } + +hilt { + enableAggregatingTask = true +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b353f48..b3127cd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 24 10:24:05 ICT 2024 +#Sun Jul 14 10:27:24 ICT 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From cb5144075396d622b1d906b8785b255aa70c7d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Wed, 17 Jul 2024 04:07:20 +0700 Subject: [PATCH 06/12] 2.0-draft19 (1583) - Update Project - Update dependencies to latest - Add built-in app webview. --- .idea/kotlinc.xml | 2 +- app/build.gradle | 5 +- app/src/main/AndroidManifest.xml | 9 + .../dutschedule/activity/BrowserActivity.kt | 288 ++++++++++++++++++ .../dutschedule/utils/FunctionExtension.kt | 16 +- .../res/drawable/ic_baseline_openinnew_24.xml | 5 + build.gradle | 7 +- 7 files changed, 320 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt create mode 100644 app/src/main/res/drawable/ic_baseline_openinnew_24.xml diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0fc3113..6d0ee1c 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3595436..d13e9ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.compose' id 'com.google.dagger.hilt.android' // https://stackoverflow.com/questions/70550883/warning-the-following-options-were-not-recognized-by-any-processor-dagger-f @@ -96,8 +97,8 @@ dependencies { // runtimeOnly 'androidx.compose.material:material-icons-extended:1.6.3' // Google Dagger/Hilt - implementation 'com.google.dagger:hilt-android:2.49' - kapt 'com.google.dagger:hilt-compiler:2.49' + implementation 'com.google.dagger:hilt-android:2.51.1' + kapt 'com.google.dagger:hilt-compiler:2.51.1' // Accompanist - Pull-to-refresh - https://mvnrepository.com/artifact/com.google.accompanist/accompanist-swiperefresh implementation 'com.google.accompanist:accompanist-swiperefresh:0.33.2-alpha' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 176d8d8..2f7f31b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -56,11 +56,20 @@ android:screenOrientation="portrait" android:theme="@style/Theme.DutSchedule" android:windowSoftInputMode="adjustResize" /> + diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt new file mode 100644 index 0000000..316b7dc --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt @@ -0,0 +1,288 @@ +package io.zoemeow.dutschedule.activity + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.view.ViewGroup +import android.webkit.URLUtil +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LinearProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import dagger.hilt.android.AndroidEntryPoint +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.model.AppearanceState +import io.zoemeow.dutschedule.model.settings.ThemeMode +import io.zoemeow.dutschedule.utils.openLink + + +@AndroidEntryPoint +class BrowserActivity : BaseActivity() { + @Composable + override fun OnPreloadOnce() { + // TODO("Not yet implemented") + } + + @Composable + override fun OnMainView( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState + ) { + MainView( + context = context, + snackBarHostState = snackBarHostState, + appearanceState = appearanceState, + startUrl = intent.getStringExtra("url") ?: "http://sv.dut.udn.vn", + onBack = { + setResult(RESULT_CANCELED) + finish() + } + ) + } + + @SuppressLint("SetJavaScriptEnabled") + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun MainView( + context: Context, + snackBarHostState: SnackbarHostState, + appearanceState: AppearanceState, + startUrl: String, + onBack: (() -> Unit)? = null + ) { + val clipboardManager: ClipboardManager = LocalClipboardManager.current + + val title = remember { mutableStateOf("Loading...") } + val url = remember { mutableStateOf("") } + val progress = remember { mutableFloatStateOf(0F) } + + var webView: WebView? = null + val canGoBack = remember { mutableStateOf(false) } + val isLoading = remember { mutableStateOf(false) } + fun goBackOrClose() { + try { + webView?.goBack() + } catch (_: Exception) { + // ex.printStackTrace() + } + } + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, + containerColor = appearanceState.containerColor, + contentColor = appearanceState.contentColor, + topBar = { + Box( + contentAlignment = Alignment.BottomCenter, + content = { + TopAppBar( + title = { + Column( + modifier = Modifier.clickable { + clipboardManager.setText(AnnotatedString(url.value)) + showSnackBar("Copied current URL to clipboard!", true) + }, + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + Text( + text = title.value, + style = MaterialTheme.typography.titleMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + if (url.value.isNotEmpty()) { + Text( + text = url.value, + style = MaterialTheme.typography.bodyMedium, + overflow = TextOverflow.Ellipsis, + maxLines = 1 + ) + } + } + }, + colors = TopAppBarDefaults.largeTopAppBarColors(containerColor = Color.Transparent, scrolledContainerColor = Color.Transparent), + navigationIcon = { + IconButton( + onClick = { + onBack?.let { it() } + }, + content = { + Icon( + Icons.Default.Clear, + context.getString(R.string.action_close), + modifier = Modifier.size(25.dp) + ) + } + ) + }, + actions = { + IconButton( + onClick = { + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, url.value) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + }, + content = { + Icon( + Icons.Default.Share, + "Share", + modifier = Modifier.size(25.dp) + ) + } + ) + IconButton( + onClick = { + try { + context.openLink( + url.value, + customTab = false + ) + } catch (_: Exception) {} + }, + content = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_baseline_openinnew_24), + "Open in default browser", + modifier = Modifier.size(25.dp) + ) + } + ) + } + ) + if (progress.floatValue != 100F) { + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = { progress.floatValue } + ) + } + } + ) + }, + floatingActionButton = { + if (!isLoading.value) { + FloatingActionButton(onClick = { webView?.reload() }) { + Icon( + Icons.Default.Refresh, + context.getString(R.string.action_refresh) + ) + } + } + } + ) { paddingValues -> + // Adding a WebView inside AndroidView + // with layout as full screen + AndroidView( + modifier = Modifier.padding(paddingValues), + factory = { + WebView(it).apply { + this.layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + this.webViewClient = object : WebViewClient() { + override fun doUpdateVisitedHistory(view: WebView?, urlChanged: String?, isReload: Boolean) { + super.doUpdateVisitedHistory(view, urlChanged, isReload) + url.value = urlChanged ?: "" + } + + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + canGoBack.value = view?.canGoBack() ?: false + } + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + if (URLUtil.isNetworkUrl(request?.url?.toString())) { + return false + } + else { + // Otherwise allow the OS to handle things like tel, mailto, etc. + request?.let { req -> + try { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(req.url?.toString())) + context.startActivity(intent) + } catch (_: Exception) {} + } + return true + } + } + } + this.webChromeClient = object : WebChromeClient() { + override fun onReceivedTitle(view: WebView?, tChanged: String?) { + super.onReceivedTitle(view, tChanged) + title.value = tChanged ?: "(unknown)" + } + + override fun onProgressChanged(view: WebView?, newProgress: Int) { + super.onProgressChanged(view, newProgress) + progress.floatValue = newProgress.toFloat() + isLoading.value = newProgress != 100 + } + } + this.settings.javaScriptEnabled = true + this.settings.setSupportZoom(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.settings.isAlgorithmicDarkeningAllowed = appearanceState.currentAppModeState == ThemeMode.DarkMode + } + webView = this + loadUrl(startUrl) + } + }, + update = { webView = it } + ) + } + + BackHandler(canGoBack.value) { + goBackOrClose() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt index 56d1430..fef8d77 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/utils/FunctionExtension.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier +import io.zoemeow.dutschedule.activity.BrowserActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged @@ -35,12 +36,15 @@ fun Context.openLink( } true -> { - val builder = CustomTabsIntent.Builder() - val defaultColors = CustomTabColorSchemeParams.Builder().build() - builder.setDefaultColorSchemeParams(defaultColors) - - val customTabsIntent = builder.build() - customTabsIntent.launchUrl(this, Uri.parse(url)) + val intent = Intent(this, BrowserActivity::class.java) + intent.putExtra("url", url) + this.startActivity(intent) +// val builder = CustomTabsIntent.Builder() +// val defaultColors = CustomTabColorSchemeParams.Builder().build() +// builder.setDefaultColorSchemeParams(defaultColors) +// +// val customTabsIntent = builder.build() +// customTabsIntent.launchUrl(this, Uri.parse(url)) } } } diff --git a/app/src/main/res/drawable/ic_baseline_openinnew_24.xml b/app/src/main/res/drawable/ic_baseline_openinnew_24.xml new file mode 100644 index 0000000..9998747 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_openinnew_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/build.gradle b/build.gradle index 932e7bf..ac078d8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.5.0' apply false - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false - id 'com.google.dagger.hilt.android' version '2.44' apply false + id 'com.android.application' version '8.5.1' apply false + id 'org.jetbrains.kotlin.android' version '2.0.0' apply false + id 'com.google.dagger.hilt.android' version '2.51.1' apply false + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.0' } From c3bc20b388467e4a2df8092ba9e9f92bcedc9be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Sun, 21 Jul 2024 17:39:41 +0700 Subject: [PATCH 07/12] 2.0-draft19 (1605) - Update Project - Update Vietnamese strings for Permission request activity. - Add tag for recognize what permission is granted in Permission request activity. - Add option to open popup (bottomsheet) when click a news in News activity. - [Decrepated] MainViewDashboard is decrepated and will removed after some releases. - Add instruction in "Forgot your password?" in Account activity. - Move all functions to menu and move refresh to title bar in built-in browser activity. --- app/build.gradle | 2 +- .../io/zoemeow/dutschedule/GlobalVariables.kt | 2 +- .../dutschedule/activity/BaseActivity.kt | 19 ++- .../dutschedule/activity/BrowserActivity.kt | 157 +++++++++++++----- .../activity/PermissionsActivity.kt | 35 ++-- .../dutschedule/model/settings/AppSettings.kt | 9 +- .../ui/component/base/SimpleCardItem.kt | 19 ++- .../component/base/SwitchWithTextInSurface.kt | 17 ++ .../dutschedule/ui/component/base/Tag.kt | 21 ++- .../ui/component/news/NewsDetailScreen.kt | 3 +- .../ui/component/news/NewsPopup.kt | 157 ++++++++++++++++++ .../PermissionInformation.kt | 44 ++++- .../settings/DialogAppBackgroundSettings.kt | 4 +- .../ui/view/main/MainViewDashboard.kt | 36 ++-- .../ui/view/main/MainViewTabView.kt | 60 +++++-- .../dutschedule/ui/view/news/MainView.kt | 28 +--- .../dutschedule/ui/view/news/NewsDetail.kt | 1 - .../ui/view/settings/ExperimentSettings.kt | 23 +++ .../dutschedule/ui/view/settings/MainView.kt | 2 +- .../dutschedule/viewmodel/MainViewModel.kt | 10 +- app/src/main/res/values-vi/strings.xml | 23 +++ app/src/main/res/values/strings.xml | 23 +++ 22 files changed, 550 insertions(+), 145 deletions(-) create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt diff --git a/app/build.gradle b/app/build.gradle index d13e9ff..6976561 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,7 +16,7 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 35 - versionCode 1574 + versionCode 1605 versionName "2.0-draft19" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt b/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt index 036f6c5..09da369 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/GlobalVariables.kt @@ -4,7 +4,7 @@ import androidx.compose.ui.unit.dp class GlobalVariables { companion object { - const val LINK_FORGOT_PASSWORD = "https://www.facebook.com/ctsvdhbkdhdn/posts/pfbid02G5sza1p8x7tEJ7S1Cac6a66EW3exgxLNmR9L26RZ8sX8xjhbEnguoeAXms31i7oxl" + const val LINK_FORGOT_PASSWORD = "https://github.com/ZoeMeow1027/DutSchedule/wiki/Changing-Password-In-DUT#qu%C3%AAn-m%E1%BA%ADt-kh%E1%BA%A9u" const val LINK_REPOSITORY = "https://github.com/ZoeMeow1027/DutSchedule" const val LINK_REPOSITORY_LICENSE = "${LINK_REPOSITORY}/blob/stable/LICENSE" const val LINK_REPOSITORY_CREDITS = "${LINK_REPOSITORY}?tab=readme-ov-file#credits-and-license" diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt index f7ead4c..9594242 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BaseActivity.kt @@ -86,18 +86,19 @@ abstract class BaseActivity: ComponentActivity() { content = { val context = LocalContext.current - val draw: Bitmap? = when (getMainViewModel().appSettings.value.backgroundImage) { + when (getMainViewModel().appSettings.value.backgroundImage) { BackgroundImageOption.None -> null BackgroundImageOption.YourCurrentWallpaper -> BackgroundImageUtil.getCurrentWallpaperBackground(context) BackgroundImageOption.PickFileFromMedia -> BackgroundImageUtil.getImageFromAppData(context) - } - if (draw != null) { - Image( - modifier = Modifier.fillMaxSize(), - bitmap = draw.asImageBitmap(), - contentDescription = "background_image", - contentScale = ContentScale.Crop - ) + }.also { wallpaper -> + if (wallpaper != null) { + Image( + modifier = Modifier.fillMaxSize(), + bitmap = wallpaper.asImageBitmap(), + contentDescription = "background_image", + contentScale = ContentScale.Crop + ) + } } @Composable diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt index 316b7dc..fc250f3 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt @@ -23,20 +23,29 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear +import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TooltipBox +import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf @@ -58,7 +67,6 @@ import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.settings.ThemeMode import io.zoemeow.dutschedule.utils.openLink - @AndroidEntryPoint class BrowserActivity : BaseActivity() { @Composable @@ -96,7 +104,7 @@ class BrowserActivity : BaseActivity() { ) { val clipboardManager: ClipboardManager = LocalClipboardManager.current - val title = remember { mutableStateOf("Loading...") } + val title = remember { mutableStateOf(context.getString(R.string.activity_browser_loading)) } val url = remember { mutableStateOf("") } val progress = remember { mutableFloatStateOf(0F) } @@ -111,6 +119,10 @@ class BrowserActivity : BaseActivity() { } } + val isMenuOpened = remember { mutableStateOf(false) } + val refreshTooltipState = rememberTooltipState() + val menuTooltipState = rememberTooltipState() + Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, @@ -123,10 +135,7 @@ class BrowserActivity : BaseActivity() { TopAppBar( title = { Column( - modifier = Modifier.clickable { - clipboardManager.setText(AnnotatedString(url.value)) - showSnackBar("Copied current URL to clipboard!", true) - }, + modifier = Modifier, horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.Center ) { @@ -162,38 +171,112 @@ class BrowserActivity : BaseActivity() { ) }, actions = { - IconButton( - onClick = { - val sendIntent: Intent = Intent().apply { - action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, url.value) - type = "text/plain" + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.action_refresh)) } - val shareIntent = Intent.createChooser(sendIntent, null) - context.startActivity(shareIntent) }, + state = refreshTooltipState, content = { - Icon( - Icons.Default.Share, - "Share", - modifier = Modifier.size(25.dp) + IconButton( + onClick = { + if (!isLoading.value) { + webView?.reload() + } + }, + content = { + if (isLoading.value) { + CircularProgressIndicator( + modifier = Modifier.size(25.dp), + strokeWidth = 2.dp + ) + } else { + Icon( + Icons.Default.Refresh, + context.getString(R.string.action_refresh) + ) + } + } ) } ) - IconButton( - onClick = { - try { - context.openLink( - url.value, - customTab = false - ) - } catch (_: Exception) {} + TooltipBox( + positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + tooltip = { + PlainTooltip { + Text(text = context.getString(R.string.action_menu)) + } }, + state = menuTooltipState, content = { - Icon( - ImageVector.vectorResource(id = R.drawable.ic_baseline_openinnew_24), - "Open in default browser", - modifier = Modifier.size(25.dp) + IconButton( + onClick = { isMenuOpened.value = true }, + content = { + Icon( + Icons.Default.MoreVert, + context.getString(R.string.action_menu), + modifier = Modifier.size(25.dp) + ) + } + ) + } + ) + DropdownMenu( + expanded = isMenuOpened.value, + onDismissRequest = { isMenuOpened.value = false }, + content = { + DropdownMenuItem( + leadingIcon = { Icon( + ImageVector.vectorResource(id = R.drawable.ic_baseline_content_copy_24), + context.getString(R.string.action_copy), + modifier = Modifier.size(25.dp) + ) }, + text = { Text(context.getString(R.string.activity_browser_copylink)) }, + onClick = { + isMenuOpened.value = false + + clipboardManager.setText(AnnotatedString(url.value)) + showSnackBar(context.getString(R.string.activity_browser_copiedlink), true) + } + ) + DropdownMenuItem( + leadingIcon = { Icon( + Icons.Default.Share, + context.getString(R.string.action_share), + modifier = Modifier.size(25.dp) + ) }, + text = { Text(context.getString(R.string.action_share)) }, + onClick = { + isMenuOpened.value = false + + val sendIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, url.value) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + } + ) + DropdownMenuItem( + leadingIcon = { Icon( + ImageVector.vectorResource(id = R.drawable.ic_baseline_openinnew_24), + context.getString(R.string.activity_browser_openindefaultbrowser), + modifier = Modifier.size(25.dp) + ) }, + text = { Text(context.getString(R.string.activity_browser_openindefaultbrowser)) }, + onClick = { + isMenuOpened.value = false + + try { + context.openLink( + url.value, + customTab = false + ) + } catch (_: Exception) {} + } ) } ) @@ -207,16 +290,6 @@ class BrowserActivity : BaseActivity() { } } ) - }, - floatingActionButton = { - if (!isLoading.value) { - FloatingActionButton(onClick = { webView?.reload() }) { - Icon( - Icons.Default.Refresh, - context.getString(R.string.action_refresh) - ) - } - } } ) { paddingValues -> // Adding a WebView inside AndroidView @@ -259,7 +332,7 @@ class BrowserActivity : BaseActivity() { this.webChromeClient = object : WebChromeClient() { override fun onReceivedTitle(view: WebView?, tChanged: String?) { super.onReceivedTitle(view, tChanged) - title.value = tChanged ?: "(unknown)" + title.value = tChanged ?: context.getString(R.string.data_unknown) } override fun onProgressChanged(view: WebView?, newProgress: Int) { @@ -269,6 +342,8 @@ class BrowserActivity : BaseActivity() { } } this.settings.javaScriptEnabled = true + this.settings.builtInZoomControls = true + this.settings.displayZoomControls = false this.settings.setSupportZoom(true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { this.settings.isAlgorithmicDarkeningAllowed = appearanceState.currentAppModeState == ThemeMode.DarkMode diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt index 4f3dbc4..7349475 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/PermissionsActivity.kt @@ -95,14 +95,15 @@ class PermissionsActivity : BaseActivity() { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, containerColor = appearanceState.containerColor, contentColor = appearanceState.contentColor, topBar = { LargeTopAppBar( - title = { Text(text = "Permissions request") }, + title = { Text(text = context.getString(R.string.activity_permissionrequest_title)) }, colors = TopAppBarDefaults.largeTopAppBarColors(containerColor = Color.Transparent, scrolledContainerColor = Color.Transparent), navigationIcon = { IconButton( @@ -130,7 +131,7 @@ class PermissionsActivity : BaseActivity() { ExtendedFloatingActionButton( onClick = { fabClicked?.let { it() } }, icon = { Icon(Icons.Default.Settings, "") }, - text = { Text("Open Android app settings") } + text = { Text(context.getString(R.string.activity_permissionrequest_action_openandroidsettings)) } ) }, actions = {} @@ -144,8 +145,7 @@ class PermissionsActivity : BaseActivity() { .padding(horizontal = 15.dp), content = { Text( - "Below is all permissions requested by this app. Click a permission to grant that. " + - "You can deny some permissions if you don't need some app features by open Android app settings.", + context.getString(R.string.activity_permissionrequest_description), modifier = Modifier.padding(vertical = 10.dp), style = MaterialTheme.typography.bodyMedium ) @@ -156,9 +156,11 @@ class PermissionsActivity : BaseActivity() { content = { permissionStatusList.forEach { item -> PermissionInformation( + context = context, title = item.name, + permissionCode = item.code, // description = "${item.code}\n\n${item.description}", - description = "\n${item.description}", + description = item.description, isRequired = false, isGranted = item.isGranted, padding = PaddingValues(bottom = 10.dp), @@ -166,7 +168,7 @@ class PermissionsActivity : BaseActivity() { clicked = { permissionRequest?.let { if (item.isGranted) { - showSnackBar("You're already granted this permission!", true) + showSnackBar(context.getString(R.string.activity_permissionrequest_snackbar_alreadygranted), true) } else it(item.code) } } @@ -200,17 +202,16 @@ class PermissionsActivity : BaseActivity() { fun getAllPermissions(context: Context): List { return listOf( checkPermissionNotification(context), - checkPermissionManageExternalStorage(), + checkPermissionManageExternalStorage(context), checkPermissionScheduleExactAlarm(context) ) } fun checkPermissionNotification(context: Context): PermissionCheckResult { return PermissionCheckResult( - name = "Notifications", + name = context.getString(R.string.activity_permissionrequest_permission_notification_title), code = "android.permission.POST_NOTIFICATIONS", - description = "Allow this app to send new announcements " + - "(news global and news subject) and other for you.", + description = context.getString(R.string.activity_permissionrequest_permission_notification_description), isGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { ContextCompat.checkSelfPermission( context, Manifest.permission.POST_NOTIFICATIONS @@ -220,13 +221,11 @@ class PermissionsActivity : BaseActivity() { } // https://stackoverflow.com/questions/73620790/android-13-how-to-request-write-external-storage - fun checkPermissionManageExternalStorage(): PermissionCheckResult { + fun checkPermissionManageExternalStorage(context: Context): PermissionCheckResult { return PermissionCheckResult( - name = "Manage External Storage", + name = context.getString(R.string.activity_permissionrequest_permission_manageexternalstorage_title), code = "android.permission.MANAGE_EXTERNAL_STORAGE", - description = "This app will use your current wallpaper as app background wallpaper. " + - "We promise not to upload or modify any data on your device, including your wallpaper. " + - "If you don't want to grant this permission, you can use \"Choose a image from media\" instead.", + description = context.getString(R.string.activity_permissionrequest_permission_manageexternalstorage_description), isGranted = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Environment.isExternalStorageManager() @@ -238,9 +237,9 @@ class PermissionsActivity : BaseActivity() { fun checkPermissionScheduleExactAlarm(context: Context): PermissionCheckResult { val alarmManager: AlarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager return PermissionCheckResult( - name = "Schedule Exact Alarm", + name = context.getString(R.string.activity_permissionrequest_permission_scheduleexactalarm_title), code = "android.permission.SCHEDULE_EXACT_ALARM", - description = "Allow this app to schedule service to update news in background for you.", + description = context.getString(R.string.activity_permissionrequest_permission_scheduleexactalarm_description), isGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { alarmManager.canScheduleExactAlarms() } else true diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/settings/AppSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/model/settings/AppSettings.kt index 8a1c9df..6438b24 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/settings/AppSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/settings/AppSettings.kt @@ -55,6 +55,9 @@ data class AppSettings( @SerializedName("appsettings.globalvariables.schoolyear") val currentSchoolYear: SchoolYearItem = SchoolYearItem(), + + @SerializedName("appsettings.behavor.clicknewsinmain") + val openNewsInModalBottomSheet: Boolean = true ): Serializable { fun clone( mainScreenDashboardView: Boolean? = null, @@ -69,7 +72,8 @@ data class AppSettings( newsBackgroundGlobalEnabled: Boolean? = null, newsBackgroundSubjectEnabled: Int? = null, newsBackgroundParseNewsSubject: Boolean? = null, - currentSchoolYear: SchoolYearItem? = null + currentSchoolYear: SchoolYearItem? = null, + openNewsInModalBottomSheet: Boolean? = null ): AppSettings { return AppSettings( mainScreenDashboardView = mainScreenDashboardView ?: this.mainScreenDashboardView, @@ -88,7 +92,8 @@ data class AppSettings( newsBackgroundGlobalEnabled = newsBackgroundGlobalEnabled ?: this.newsBackgroundGlobalEnabled, newsBackgroundSubjectEnabled = newsBackgroundSubjectEnabled ?: this.newsBackgroundSubjectEnabled, newsBackgroundParseNewsSubject = newsBackgroundParseNewsSubject ?: this.newsBackgroundParseNewsSubject, - currentSchoolYear = currentSchoolYear ?: this.currentSchoolYear + currentSchoolYear = currentSchoolYear ?: this.currentSchoolYear, + openNewsInModalBottomSheet = openNewsInModalBottomSheet ?: this.openNewsInModalBottomSheet ) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt index 6c41f8e..a0babaf 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SimpleCardItem.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @Composable @@ -45,7 +46,9 @@ fun SimpleCardItem( } ) { Row( - modifier = Modifier.fillMaxWidth().padding(bottom = 5.dp), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 5.dp), horizontalArrangement = if (isTitleCentered) Arrangement.Center else Arrangement.Start, content = { Text( @@ -58,4 +61,18 @@ fun SimpleCardItem( ) content?.let { it() } } +} + +@Preview +@Composable +private fun Preview() { + SimpleCardItem( + title = "Abc", + content = { + Text("1") + Text("2") + Text("3") + Text("4") + } + ) } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SwitchWithTextInSurface.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SwitchWithTextInSurface.kt index 157f822..a1c0ba9 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SwitchWithTextInSurface.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/SwitchWithTextInSurface.kt @@ -22,7 +22,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.zoemeow.dutschedule.R @Composable fun SwitchWithTextInSurface( @@ -98,4 +101,18 @@ fun SwitchWithTextInSurface( } ) } +} + +@Preview +@Composable +private fun Preview() { + val context = LocalContext.current + SwitchWithTextInSurface( + text = context.getString(R.string.settings_newsnotify_fetchnewsinbackground), + enabled = true, + checked = true, + onCheckedChange = { + // Refresh news state changed, default is 30 minutes + } + ) } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/Tag.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/Tag.kt index 78c856f..d14d4fc 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/Tag.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/base/Tag.kt @@ -2,6 +2,7 @@ package io.zoemeow.dutschedule.ui.component.base import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -14,8 +15,8 @@ import androidx.compose.ui.unit.sp @Composable fun Tag( text: String = "", - textColor: Color = Color.Black, - backColor: Color = Color.White + textColor: Color = ButtonDefaults.elevatedButtonColors().contentColor, + backColor: Color = ButtonDefaults.elevatedButtonColors().containerColor ) { Surface( shape = RoundedCornerShape(20.dp), @@ -37,11 +38,7 @@ fun Tag( @Preview @Composable private fun TagPreview() { - Tag( - "Hello", - textColor = Color.Black, - backColor = Color.Green - ) + Tag("Hello") } @Preview @@ -53,3 +50,13 @@ private fun TagPreview2() { backColor = Color.Red ) } + +@Preview +@Composable +private fun TagPreview3() { + Tag( + "Successful tag", + textColor = Color.Black, + backColor = Color.Green + ) +} diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt index c09ad0b..13806d1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.text.selection.SelectionContainer @@ -67,7 +68,7 @@ private fun NewsDetailBody_NewsGlobal( ) { Box( modifier = Modifier - .fillMaxSize() + .wrapContentSize() .padding(padding) .verticalScroll(rememberScrollState()) ) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt new file mode 100644 index 0000000..2ad882b --- /dev/null +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt @@ -0,0 +1,157 @@ +package io.zoemeow.dutschedule.ui.component.news + +import android.content.Context +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.waterfall +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import io.dutwrapper.dutwrapper.model.enums.NewsType +import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem +import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem +import io.zoemeow.dutschedule.R +import io.zoemeow.dutschedule.activity.NewsActivity +import io.zoemeow.dutschedule.model.AppearanceState +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NewsPopup( + context: Context, + snackBarHostState: SnackbarHostState? = null, + appearanceState: AppearanceState, + isVisible: Boolean = false, + newsType: String? = null, + newsData: String? = null, + onLinkClicked: ((String) -> Unit)? = null, + onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) + onDismiss: () -> Unit +) { + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + val scope = rememberCoroutineScope() + + if (isVisible) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + sheetState = sheetState, + content = { + Scaffold( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 5.dp), + snackbarHost = { snackBarHostState?.let { SnackbarHost(hostState = it) } }, + containerColor = appearanceState.containerColor.copy(alpha = 1f), + contentColor = appearanceState.contentColor, + topBar = { + TopAppBar( + title = { Text(context.getString(R.string.news_detail_title)) }, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = Color.Transparent, + scrolledContainerColor = Color.Transparent + ), + actions = { + IconButton( + onClick = { + scope.launch { + sheetState.hide() + }.invokeOnCompletion { + onDismiss() + } + }, + content = { + Icon( + Icons.Default.Clear, + context.getString(R.string.action_close), + modifier = Modifier.size(25.dp) + ) + } + ) + } + ) + }, + floatingActionButton = { + if (newsType == NewsActivity.NEWSTYPE_NEWSSUBJECT) { + ExtendedFloatingActionButton( + content = { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Add, context.getString(R.string.news_detail_addtofilter_fab)) + Spacer(modifier = Modifier.size(5.dp)) + Text(context.getString(R.string.news_detail_addtofilter_fab)) + } + }, + onClick = { + try { + // TODO: Develop a add news filter function for news subject detail. + onMessageReceived(context.getString(R.string.feature_not_ready), true, null, null) + } catch (ex: Exception) { + ex.printStackTrace() + onMessageReceived(context.getString(R.string.news_detail_addtofilter_failed), true, null, null) + } + } + ) + } + } + ) { paddingValues -> + when (newsType) { + NewsActivity.NEWSTYPE_NEWSGLOBAL -> { + NewsDetailScreen( + context = context, + padding = paddingValues, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), + newsType = NewsType.Global, + linkClicked = { link -> + onLinkClicked?.let { it(link) } + } + ) + } + NewsActivity.NEWSTYPE_NEWSSUBJECT -> { + NewsDetailScreen( + context = context, + padding = paddingValues, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsGlobalItem, + newsType = NewsType.Subject, + linkClicked = { link -> + onLinkClicked?.let { it(link) } + } + ) + } + else -> { } + } + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/permissionrequest/PermissionInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/permissionrequest/PermissionInformation.kt index 060cb54..cbb38e5 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/permissionrequest/PermissionInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/permissionrequest/PermissionInformation.kt @@ -1,30 +1,39 @@ package io.zoemeow.dutschedule.ui.component.permissionrequest +import android.content.Context import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer +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.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import io.zoemeow.dutschedule.R @Composable fun PermissionInformation( + context: Context, title: String, + permissionCode: String? = null, description: String, isRequired: Boolean = false, isGranted: Boolean = false, @@ -55,9 +64,38 @@ fun PermissionInformation( fontSize = 20.sp, fontWeight = FontWeight.W600, ) - Spacer(modifier = Modifier.size(3.dp)) + permissionCode?.let { + Text( + it, + style = MaterialTheme.typography.bodyMedium + ) + } + Surface( + modifier = Modifier.padding(vertical = 5.dp), + color = if (isGranted) Color.Green else Color.Red, + contentColor = if (isGranted) Color.Black else Color.White, + shape = RoundedCornerShape(15.dp) + ) { + Row( + modifier = Modifier.padding(horizontal = 10.dp, vertical = 3.dp), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + when (isGranted) { + true -> { + Icon(Icons.Default.Check, context.getString(R.string.activity_permissionrequest_status_allowed)) + Text(context.getString(R.string.activity_permissionrequest_status_allowed)) + } + false -> { + Icon(Icons.Default.Clear, context.getString(R.string.activity_permissionrequest_status_declined)) + Text(context.getString(R.string.activity_permissionrequest_status_declined)) + } + } + } + } Text( description, + modifier = Modifier.padding(top = 10.dp), style = MaterialTheme.typography.bodyMedium ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt index ff8623a..e969ea7 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt @@ -61,7 +61,7 @@ fun DialogAppBackgroundSettings( "\n(${context.getString(R.string.settings_dialog_wallpaperbackground_choice_currentwallpaper_disa14)})" } // Permission is not granted. - (!PermissionsActivity.checkPermissionManageExternalStorage().isGranted) -> { + (!PermissionsActivity.checkPermissionManageExternalStorage(context = context).isGranted) -> { "\n(${context.getString(R.string.settings_dialog_wallpaperbackground_choice_currentwallpaper_dismisperext)})" } // Else, no exception @@ -71,7 +71,7 @@ fun DialogAppBackgroundSettings( isSelected = value == BackgroundImageOption.YourCurrentWallpaper, onClick = { val compSdk = Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE - val compPer = PermissionsActivity.checkPermissionManageExternalStorage().isGranted + val compPer = PermissionsActivity.checkPermissionManageExternalStorage(context = context).isGranted if (compSdk && compPer) { onDismiss() onValueChanged(BackgroundImageOption.YourCurrentWallpaper) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index ff505c8..acd46ef 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -240,24 +240,24 @@ fun Activity_MainView_Dashboard_Body( }.toList(), opacity = appearanceState.componentOpacity ) - AffectedLessonsSummaryItem( - padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), - hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, - isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, - clicked = { - if (mainViewModel.accountSession.accountSession.processState.value != ProcessState.Successful) { - onLoginRequested?.let { it() } - } - }, - affectedList = arrayListOf( - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12", - "ie1i0921d - i029di12" - ), - opacity = appearanceState.componentOpacity - ) +// AffectedLessonsSummaryItem( +// padding = PaddingValues(bottom = 10.dp, start = 15.dp, end = 15.dp), +// hasLoggedIn = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Successful, +// isLoading = mainViewModel.accountSession.accountSession.processState.value == ProcessState.Running || mainViewModel.accountSession.subjectSchedule.processState.value == ProcessState.Running, +// clicked = { +// if (mainViewModel.accountSession.accountSession.processState.value != ProcessState.Successful) { +// onLoginRequested?.let { it() } +// } +// }, +// affectedList = arrayListOf( +// "ie1i0921d - i029di12", +// "ie1i0921d - i029di12", +// "ie1i0921d - i029di12", +// "ie1i0921d - i029di12", +// "ie1i0921d - i029di12" +// ), +// opacity = appearanceState.componentOpacity +// ) } else -> { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt index 15c6d3a..5db5ce2 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewTabView.kt @@ -38,9 +38,11 @@ import io.zoemeow.dutschedule.activity.SettingsActivity import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.NavBarItem import io.zoemeow.dutschedule.model.settings.BackgroundImageOption +import io.zoemeow.dutschedule.ui.component.news.NewsPopup import io.zoemeow.dutschedule.ui.view.account.Activity_Account import io.zoemeow.dutschedule.ui.view.news.Activity_News import io.zoemeow.dutschedule.utils.BackgroundImageUtil +import io.zoemeow.dutschedule.utils.openLink import io.zoemeow.dutschedule.viewmodel.MainViewModel @Composable @@ -58,8 +60,14 @@ fun Activity_MainView_MainViewTabView( val backStackEntry by navController.currentBackStackEntryAsState() val currentRoute = backStackEntry?.destination?.route + // Notification Scaffold visible val isNotificationOpened = remember { mutableStateOf(false) } + // News Detail Scaffold popup visible + val isNewsDetailScaffoldOpened = remember { mutableStateOf(false) } + val newsDetailType = remember { mutableStateOf(null) } + val newsDetailData = remember { mutableStateOf(null) } + Scaffold( modifier = Modifier.fillMaxSize(), snackbarHost = { SnackbarHost(hostState = snackBarHostState) }, @@ -158,6 +166,23 @@ fun Activity_MainView_MainViewTabView( val intent = Intent(context, NewsActivity::class.java) intent.action = NewsActivity.INTENT_SEARCHACTIVITY context.startActivity(intent) + }, + onNewsClicked = { newsType, newsData -> + if (mainViewModel.appSettings.value.openNewsInModalBottomSheet) { + newsDetailType.value = newsType + newsDetailData.value = newsData + isNewsDetailScaffoldOpened.value = true + } else { + context.startActivity( + Intent( + context, + NewsActivity::class.java + ).also { intent -> + intent.action = NewsActivity.INTENT_NEWSDETAILACTIVITY + intent.putExtra("type", newsType) + intent.putExtra("data", newsData) + }) + } } ) } @@ -172,20 +197,6 @@ fun Activity_MainView_MainViewTabView( } ) } - -// composable(NavBarItem.settings.route) { -// Activity_Settings( -// context = context, -// appearanceState = appearanceState, -// mainViewModel = getMainViewModel(), -// mediaRequest = { -// pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) -// }, -// onMessageReceived = { text, clearPrevious, actionText, action -> -// showSnackBar(text = text, clearPrevious = clearPrevious, actionText = actionText, action = action) -// } -// ) -// } } } ) @@ -245,7 +256,26 @@ fun Activity_MainView_MainViewTabView( } } ) - BackHandler(isNotificationOpened.value) { + NewsPopup( + isVisible = isNewsDetailScaffoldOpened.value, + context = context, + snackBarHostState = snackBarHostState, + appearanceState = appearanceState, + onMessageReceived = onMessageReceived, + newsType = newsDetailType.value, + newsData = newsDetailData.value, + onDismiss = { isNewsDetailScaffoldOpened.value = false }, + onLinkClicked = { link -> + context.openLink( + url = link, + customTab = mainViewModel.appSettings.value.openLinkInsideApp + ) + }, + ) + BackHandler(isNotificationOpened.value || isNewsDetailScaffoldOpened.value) { + if (isNewsDetailScaffoldOpened.value) { + isNewsDetailScaffoldOpened.value = false + } if (isNotificationOpened.value) { isNotificationOpened.value = false } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt index 43e81ac..9106c72 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt @@ -1,7 +1,6 @@ package io.zoemeow.dutschedule.ui.view.news import android.content.Context -import android.content.Intent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -67,6 +66,7 @@ fun Activity_News( appearanceState: AppearanceState, mainViewModel: MainViewModel, searchRequested: (() -> Unit)? = null, + onNewsClicked: ((String?, String?) -> Unit)? = null, onBack: (() -> Unit)? = null ) { val pagerState = rememberPagerState(initialPage = 0, pageCount = { 2 }) @@ -234,15 +234,10 @@ fun Activity_News( processState = mainViewModel.newsInstance.newsGlobal.processState.value, opacity = appearanceState.componentOpacity, itemClicked = { newsItem -> - context.startActivity( - Intent( - context, - NewsActivity::class.java - ).also { - it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY - it.putExtra("type", NewsActivity.NEWSTYPE_NEWSGLOBAL) - it.putExtra("data", Gson().toJson(newsItem)) - }) + onNewsClicked?.let { it( + NewsActivity.NEWSTYPE_NEWSGLOBAL, + Gson().toJson(newsItem) + ) } }, endOfListReached = { CoroutineScope(Dispatchers.Main).launch { @@ -263,15 +258,10 @@ fun Activity_News( processState = mainViewModel.newsInstance.newsSubject.processState.value, opacity = appearanceState.componentOpacity, itemClicked = { newsItem -> - context.startActivity( - Intent( - context, - NewsActivity::class.java - ).also { - it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY - it.putExtra("type", NewsActivity.NEWSTYPE_NEWSSUBJECT) - it.putExtra("data", Gson().toJson(newsItem)) - }) + onNewsClicked?.let { it( + NewsActivity.NEWSTYPE_NEWSSUBJECT, + Gson().toJson(newsItem) + ) } }, endOfListReached = { CoroutineScope(Dispatchers.Main).launch { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt index 7fc0360..0a671aa 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt @@ -17,7 +17,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt index ddd3155..b69f9a4 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/ExperimentSettings.kt @@ -115,6 +115,29 @@ fun Activity_Settings_ExperimentSettings( } ) DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) + ContentRegion( + modifier = Modifier.padding(top = 10.dp), + textModifier = Modifier.padding(horizontal = 20.dp), + text = context.getString(R.string.settings_experiment_category_news), + content = { + OptionSwitchItem( + modifierInside = Modifier.padding(horizontal = 20.dp, vertical = 15.dp), + title = context.getString(R.string.settings_experiment_option_opennewsinpopup), + isVisible = true, + isEnabled = true, + isChecked = mainViewModel.appSettings.value.openNewsInModalBottomSheet, + description = context.getString(R.string.settings_experiment_option_opennewsinpopup_description), + onValueChanged = { value -> + mainViewModel.appSettings.value = + mainViewModel.appSettings.value.clone( + openNewsInModalBottomSheet = value + ) + mainViewModel.saveApplicationSettings(saveUserSettings = true) + } + ) + } + ) + DividerItem(padding = PaddingValues(top = 5.dp, bottom = 15.dp)) ContentRegion( modifier = Modifier.padding(top = 10.dp), textModifier = Modifier.padding(horizontal = 20.dp), diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt index 4ceda88..9b0d092 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/MainView.kt @@ -369,7 +369,7 @@ fun Activity_Settings( BackgroundImageOption.YourCurrentWallpaper -> { val compPer = - PermissionsActivity.checkPermissionManageExternalStorage().isGranted + PermissionsActivity.checkPermissionManageExternalStorage(context = context).isGranted if (compPer) { mainViewModel.appSettings.value = mainViewModel.appSettings.value.clone( diff --git a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt index 6bd3f4b..b41d2cb 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt @@ -150,11 +150,6 @@ class MainViewModel @Inject constructor( ) { launchOnScope( script = { - // App settings - appSettings.value = fileModuleRepository.getAppSettings() - accountSession.setAccountSession(fileModuleRepository.getAccountSession()) - accountSession.setSchoolYear(schoolYearItem = appSettings.value.currentSchoolYear) - // Notification cache notificationHistory.clear() notificationHistory.addAll(fileModuleRepository.getNotificationHistory()) @@ -192,6 +187,11 @@ class MainViewModel @Inject constructor( private val _runOnStartup = mutableStateOf(false) init { if (!_runOnStartup.value) { + // App settings + appSettings.value = fileModuleRepository.getAppSettings() + accountSession.setAccountSession(fileModuleRepository.getAccountSession()) + accountSession.setSchoolYear(schoolYearItem = appSettings.value.currentSchoolYear) + loadApplicationSettings( onCompleted = { accountSession.reLogin(force = true) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 5c9275b..f820f61 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -15,11 +15,13 @@ Cho phép Xác nhận Làm mới + Menu Tìm kiếm Hoàn tác Đóng Hiển thị Ẩn + Chia sẻ Đã chọn Thông tin @@ -135,6 +137,9 @@ Năm học hiện tại Năm: 20%1$d-20%2$d, học kỳ: %3$s%4$s (học kỳ hè) + Tin tức + Mở tin tức đã chọn trong popup + Nếu bị vô hiệu hóa, chúng tôi sẽ mở tin tức trong màn hình mới Hiển thị Bạn cần bật hình nền ứng dụng để tùy chọn có tác dụng Độ mờ hình nền & thành phần điều khiển @@ -384,6 +389,24 @@ DutSchedule - Cài đặt DutSchedule - Linh tinh Cập nhật tin tức trong nền + + Đang tải… + Sao chép liên kết + Đã sao chép liên kết hiện tại vào bộ nhớ tạm! + Mở trong trình duyệt mặc định + + Danh sách quyền được yêu cầu + Dưới đây là tất cả các quyền được yêu cầu bởi ứng dụng này. Nhấn vào một quyền để cấp quyền đó. Nếu bạn không thể cấp hoặc muốn thu hồi quyền, bạn có thể làm điều đó bằng cách mở Cài đặt ứng dụng Android bên dưới. + Đã cho phép + Đã từ chối + Bạn đã cho phép quyền này! + Mở cài đặt ứng dụng Android + Thông báo + Cho phép ứng dụng này gửi thông báo mới (tin tức chung và tin tức lớp học phần), cập nhật ứng dụng… Bạn vẫn có thể thay đổi nhóm thông báo nào sẽ thông báo cho bạn trong cài đặt ứng dụng. + Truy cập tất cả tập tin + Ứng dụng này sẽ sử dụng quyền đó để lấy hình nền hiện tại của bạn làm hình nền ứng dụng. Đó là tất cả! Bạn vẫn có thể chọn ảnh làm hình nền ứng dụng mà không cần quyền này. + Lên lịch báo thức chính xác + Cho phép ứng dụng này lên lịch dịch vụ để cập nhật tin tức trong nền cho bạn. (không rõ) (không có điểm) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 19ec04b..fe77c0d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,11 +15,13 @@ Grant Confirm Refresh + Menu Search Undo Close Display Hide + Share Selected Information @@ -135,6 +137,9 @@ Current school year Year: 20%1$d-20%2$d, semester: %3$s%4$s (in summer) + News + Open clicked news in popup + If disabled, we will open news in new screen Appearance You need enable background image to take effect Background & component opacity @@ -384,6 +389,24 @@ DutSchedule - Settings DutSchedule - Miscellaneous News background update service + + Loading… + Copy link + Copied current link to clipboard! + Open in default browser + + Permissions request list + Below is all permissions requested by this app. Click a permission to grant that. If you can\'t grant or want to revoke a permission, you can still do that by open Android app settings below. + Allowed + Declined + You\'re already granted this permission! + Open Android app settings + Notifications + Allow this app send new announcements (global news and subject news), app updates… You can still change what notification group should notify you in app settings. + All file access + This app will use this permission to use your current wallpaper as app background wallpaper. That\'s it! You can still choose your image as your app background without this permission. + Schedule Exact Alarm + Allow this app to schedule service to update news in background for you. (unknown) (no score) From aef65206e5b794c071764f5ab9a34ad554717460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:35:48 +0700 Subject: [PATCH 08/12] 2.0-draft20 (1638) - Updated dependencies to latest. - Need to modify any file to update functions about dutwrapper dependency. - This application will no longer direct links when URL Protocol is used (ex: example://,...). A option that enable them will be available in future version. --- CHANGELOG.md | 8 ++- app/build.gradle | 18 +++--- .../dutschedule/activity/BrowserActivity.kt | 5 +- .../model/account/AccountSession.kt | 6 +- .../model/account/DUTAccountInstance.kt | 20 +++---- .../dutschedule/model/news/DUTNewsInstance.kt | 4 +- .../dutschedule/model/news/NewsGlobalItem.kt | 10 ++-- .../model/news/NewsSearchHistory.kt | 5 +- .../dutschedule/model/news/NewsSubjectItem.kt | 10 ++-- .../repository/DutRequestRepository.kt | 56 +++++++++---------- .../repository/FileModuleRepository.kt | 15 ++--- .../service/NewsBackgroundUpdateService.kt | 42 +++++++------- .../account/AccountSubjectFeeInformation.kt | 6 +- .../account/AccountSubjectInformation.kt | 6 +- .../account/AccountSubjectMoreInformation.kt | 16 +++--- .../ui/component/account/SubjectResult.kt | 8 +-- .../component/main/DateAndTimeSummaryItem.kt | 4 +- .../component/main/LessonsTodaySummaryItem.kt | 6 +- .../ui/component/news/NewsDetailScreen.kt | 54 +++++++++--------- .../ui/component/news/NewsListPage.kt | 8 +-- .../ui/component/news/NewsPopup.kt | 21 ++----- .../news/NewsSearchOptionAndHistory.kt | 33 ++++++----- .../ui/component/news/NewsSearchResult.kt | 8 +-- .../settings/DialogSchoolYearSettings.kt | 1 - .../ui/view/account/SubjectInformation.kt | 4 +- .../ui/view/account/TrainingResult.kt | 4 +- .../ui/view/account/TrainingSubjectResult.kt | 8 +-- .../ui/view/main/MainViewDashboard.kt | 4 +- .../dutschedule/ui/view/news/MainView.kt | 4 +- .../dutschedule/ui/view/news/NewsDetail.kt | 14 ++--- .../dutschedule/ui/view/news/NewsSearch.kt | 4 +- .../dutschedule/viewmodel/MainViewModel.kt | 5 +- .../viewmodel/NewsSearchViewModel.kt | 13 ++--- 33 files changed, 209 insertions(+), 221 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd557bc..0588263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,13 @@ ## Known issues - `Your current wallpaper` option in app background settings will be disabled on Android 14 and later. You can check why in `Issue` tab in repository. -## 2.0-draft19 (1467) +## 2.0-draft20 (1638) (Working in progress) +- [Imporvement] Updated dependencies to latest. + - [Change] Need to modify any file to update functions about `dutwrapper` dependency. +- [Change] This application will no longer direct links when URL Protocol is used (ex: example://,...). + - A option that enable them will be available in future version. + +## 2.0-draft19 (1605) - [Rework] Account Training Subject Result screen. - [Improve] Update dependencies to latest. - This will resolve crash app when sv.dut.udn.vn is not reachable. diff --git a/app/build.gradle b/app/build.gradle index 6976561..16afe61 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId "io.zoemeow.dutschedule" minSdk 21 targetSdkVersion 35 - versionCode 1605 - versionName "2.0-draft19" + versionCode 1638 + versionName "2.0-draft20" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -56,8 +56,8 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.3' - implementation 'androidx.activity:activity-compose:1.9.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4' + implementation 'androidx.activity:activity-compose:1.9.1' implementation platform('androidx.compose:compose-bom:2024.06.00') implementation "androidx.compose.ui:ui:1.6.8" implementation "androidx.compose.ui:ui-tooling-preview:1.6.8" @@ -78,12 +78,12 @@ dependencies { implementation 'androidx.navigation:navigation-compose:2.7.7' // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-ktx - runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3' + runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4' // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-compose - runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.3' + runtimeOnly 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.4' // https://mvnrepository.com/artifact/androidx.fragment/fragment-ktx - runtimeOnly 'androidx.fragment:fragment-ktx:1.8.1' + runtimeOnly 'androidx.fragment:fragment-ktx:1.8.2' // https://mvnrepository.com/artifact/androidx.compose.material3/material3 implementation 'androidx.compose.material3:material3:1.2.1' @@ -110,7 +110,7 @@ dependencies { runtimeOnly 'com.google.accompanist:accompanist-pager-indicators:0.33.2-alpha' // Jsoup HTML parser library - https://mvnrepository.com/artifact/org.jsoup/jsoup - implementation 'org.jsoup:jsoup:1.17.2' + implementation 'org.jsoup:jsoup:1.18.1' // Google Gson - https://mvnrepository.com/artifact/com.google.code.gson/gson implementation 'com.google.code.gson:gson:2.11.0' @@ -123,7 +123,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.5.0' - implementation 'com.github.dutwrapper:dutwrapper-java:v1.10.1' + implementation 'com.github.dutwrapper:dutwrapper-java:204328e7f2' implementation 'com.google.android.material:material:1.12.0' diff --git a/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt index fc250f3..0026adf 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/activity/BrowserActivity.kt @@ -321,8 +321,9 @@ class BrowserActivity : BaseActivity() { // Otherwise allow the OS to handle things like tel, mailto, etc. request?.let { req -> try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(req.url?.toString())) - context.startActivity(intent) +// val intent = Intent(Intent.ACTION_VIEW, Uri.parse(req.url?.toString())) +// context.startActivity(intent) + // TODO: User choice about pass this url protocol to app activity } catch (_: Exception) {} } return true diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/account/AccountSession.kt b/app/src/main/java/io/zoemeow/dutschedule/model/account/AccountSession.kt index 678751f..80c4856 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/account/AccountSession.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/account/AccountSession.kt @@ -1,7 +1,7 @@ package io.zoemeow.dutschedule.model.account import com.google.gson.annotations.SerializedName -import io.dutwrapper.dutwrapper.Account +import io.dutwrapper.dutwrapper.Accounts import java.io.Serializable data class AccountSession( @@ -36,8 +36,8 @@ data class AccountSession( ) } - fun toAccountSessionSuper(): Account.Session { - return Account.Session( + fun toAccountSessionSuper(): Accounts.Session { + return Accounts.Session( sessionId, viewState, viewStateGenerator diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt b/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt index 8c8fd6c..4d83ff9 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/account/DUTAccountInstance.kt @@ -3,10 +3,10 @@ package io.zoemeow.dutschedule.model.account import android.util.Log import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import io.dutwrapper.dutwrapper.model.accounts.AccountInformation -import io.dutwrapper.dutwrapper.model.accounts.SubjectFeeItem -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem -import io.dutwrapper.dutwrapper.model.accounts.trainingresult.AccountTrainingStatus +import io.dutwrapper.dutwrapper.AccountInformation.StudentInformation +import io.dutwrapper.dutwrapper.AccountInformation.SubjectFee +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation +import io.dutwrapper.dutwrapper.AccountInformation.TrainingStatus import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.VariableListState import io.zoemeow.dutschedule.model.VariableState @@ -40,10 +40,10 @@ class DUTAccountInstance( @Suppress("KDocUnresolvedReference") val accountSession: VariableState = VariableState(data = mutableStateOf(null)) private val schoolYear: MutableState = mutableStateOf(null) - val subjectSchedule: VariableListState = VariableListState() - val subjectFee: VariableListState = VariableListState() - val accountInformation: VariableState = VariableState(data = mutableStateOf(null)) - val accountTrainingStatus: VariableState = VariableState(data = mutableStateOf(null)) + val subjectSchedule: VariableListState = VariableListState() + val subjectFee: VariableListState = VariableListState() + val accountInformation: VariableState = VariableState(data = mutableStateOf(null)) + val accountTrainingStatus: VariableState = VariableState(data = mutableStateOf(null)) private fun checkVariable(): Boolean { return when { @@ -65,12 +65,12 @@ class DUTAccountInstance( this.accountSession.data.value = accountSession.clone() } - fun setSubjectScheduleCache(data: List) { + fun setSubjectScheduleCache(data: List) { this.subjectSchedule.data.clear() this.subjectSchedule.data.addAll(data) } - fun getSubjectScheduleCache(): List { + fun getSubjectScheduleCache(): List { return this.subjectSchedule.data.toList() } diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt b/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt index b587c50..60ebc4e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/news/DUTNewsInstance.kt @@ -105,7 +105,7 @@ class DUTNewsInstance( val anyMatch = newsGlobal.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date && newsSourceItem.title == newsTargetItem.title - && newsSourceItem.contentString == newsTargetItem.contentString + && newsSourceItem.content == newsTargetItem.content } val anyNeedUpdated = newsGlobal.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date @@ -204,7 +204,7 @@ class DUTNewsInstance( val anyMatch = newsSubject.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date && newsSourceItem.title == newsTargetItem.title - && newsSourceItem.contentString == newsTargetItem.contentString + && newsSourceItem.content == newsTargetItem.content } val anyNeedUpdated = newsSubject.data.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsGlobalItem.kt b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsGlobalItem.kt index 381be94..1f3de5e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsGlobalItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsGlobalItem.kt @@ -1,17 +1,19 @@ package io.zoemeow.dutschedule.model.news +import io.dutwrapper.dutwrapper.News + data class NewsGlobalItem( var updated: Boolean = false -) : io.dutwrapper.dutwrapper.model.news.NewsGlobalItem() { - fun update(newsItem: io.dutwrapper.dutwrapper.model.news.NewsGlobalItem) { +) : News.NewsItem() { + fun update(newsItem: News.NewsItem) { if (this.title == newsItem.title && this.date == newsItem.date) { this.updated = true } this.title = newsItem.title this.content = newsItem.content - this.contentString = newsItem.contentString - this.links = newsItem.links + this.contentHtml = newsItem.contentHtml + this.resources = newsItem.resources this.date = newsItem.date } } \ No newline at end of file diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSearchHistory.kt b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSearchHistory.kt index 4ed5b83..e902cea 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSearchHistory.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSearchHistory.kt @@ -1,10 +1,9 @@ package io.zoemeow.dutschedule.model.news -import io.dutwrapper.dutwrapper.model.enums.NewsSearchType -import io.dutwrapper.dutwrapper.model.enums.NewsType +import io.dutwrapper.dutwrapper.News data class NewsSearchHistory( - val query: String, val newsMethod: NewsSearchType, val newsType: NewsType + val query: String, val newsMethod: News.NewsSearchType, val newsType: News.NewsType ) { fun isEqual(item: NewsSearchHistory, caseSensitive: Boolean = false): Boolean { return (if (caseSensitive) item.query == this.query else item.query.lowercase() == this.query.lowercase()) && item.newsType == this.newsType && item.newsMethod == this.newsMethod diff --git a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSubjectItem.kt b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSubjectItem.kt index 068d7ad..23be307 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSubjectItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/model/news/NewsSubjectItem.kt @@ -1,13 +1,15 @@ package io.zoemeow.dutschedule.model.news +import io.dutwrapper.dutwrapper.News + data class NewsSubjectItem( var updated: Boolean = false -) : io.dutwrapper.dutwrapper.model.news.NewsSubjectItem() { - fun update(newsItem: io.dutwrapper.dutwrapper.model.news.NewsSubjectItem) { +) : News.NewsSubjectItem(News.NewsItem()) { + fun update(newsItem: News.NewsSubjectItem) { this.title = newsItem.title + this.contentHtml = newsItem.contentHtml this.content = newsItem.content - this.contentString = newsItem.contentString - this.links = newsItem.links + this.resources = newsItem.resources this.date = newsItem.date this.affectedClass = newsItem.affectedClass diff --git a/app/src/main/java/io/zoemeow/dutschedule/repository/DutRequestRepository.kt b/app/src/main/java/io/zoemeow/dutschedule/repository/DutRequestRepository.kt index f444c75..e51de67 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/repository/DutRequestRepository.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/repository/DutRequestRepository.kt @@ -1,14 +1,10 @@ package io.zoemeow.dutschedule.repository -import io.dutwrapper.dutwrapper.Account +import io.dutwrapper.dutwrapper.AccountInformation +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation +import io.dutwrapper.dutwrapper.AccountInformation.TrainingStatus +import io.dutwrapper.dutwrapper.Accounts import io.dutwrapper.dutwrapper.News -import io.dutwrapper.dutwrapper.model.accounts.AccountInformation -import io.dutwrapper.dutwrapper.model.accounts.SubjectFeeItem -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem -import io.dutwrapper.dutwrapper.model.accounts.trainingresult.AccountTrainingStatus -import io.dutwrapper.dutwrapper.model.enums.NewsSearchType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem -import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem import io.zoemeow.dutschedule.model.account.AccountSession import io.zoemeow.dutschedule.model.account.SchoolYearItem import kotlinx.coroutines.CoroutineScope @@ -18,9 +14,9 @@ import kotlinx.coroutines.launch class DutRequestRepository { fun getNewsGlobal( page: Int = 1, - searchType: NewsSearchType? = null, + searchType: News.NewsSearchType? = null, searchQuery: String? = null - ): ArrayList { + ): ArrayList { return try { News.getNewsGlobal(page, searchType, searchQuery) } catch (ex: Exception) { @@ -31,9 +27,9 @@ class DutRequestRepository { fun getNewsSubject( page: Int = 1, - searchType: NewsSearchType? = null, + searchType: News.NewsSearchType? = null, searchQuery: String? = null - ): ArrayList { + ): ArrayList { return try { News.getNewsSubject(page, searchType, searchQuery) } catch (ex: Exception) { @@ -59,17 +55,17 @@ class DutRequestRepository { run { if (accountSession.sessionId == null) return@run false if (System.currentTimeMillis() > accountSession.sessionLastRequest + (1000 * 60 * 5)) return@run false - if (!Account.isLoggedIn(accountSession.toAccountSessionSuper())) return@run false + if (!Accounts.isLoggedIn(accountSession.toAccountSessionSuper())) return@run false if (forceLogin) return@run false return@run true } -> true (accountSession.accountAuth.isValidLogin()) -> { try { - val session = Account.getSession() + val session = Accounts.getSession() - Account.login( + Accounts.login( session, - Account.AuthInfo( + Accounts.AuthInfo( accountSession.accountAuth.username, accountSession.accountAuth.password ) @@ -77,7 +73,7 @@ class DutRequestRepository { // Return here, after send data on result // If successful - if (Account.isLoggedIn(session)) { + if (Accounts.isLoggedIn(session)) { onSessionChanged?.let { it( session.sessionId, System.currentTimeMillis(), @@ -112,7 +108,7 @@ class DutRequestRepository { return try { CoroutineScope(Dispatchers.IO).launch { kotlin.runCatching { - Account.logout(accountSession.toAccountSessionSuper()) + Accounts.logout(accountSession.toAccountSessionSuper()) } } true @@ -125,10 +121,10 @@ class DutRequestRepository { fun getSubjectSchedule( accountSession: AccountSession, schoolYearItem: SchoolYearItem - ): ArrayList? { + ): ArrayList? { return try { - if (Account.isLoggedIn(accountSession.toAccountSessionSuper())) { - Account.fetchSubjectSchedule( + if (Accounts.isLoggedIn(accountSession.toAccountSessionSuper())) { + Accounts.fetchSubjectSchedule( accountSession.toAccountSessionSuper(), schoolYearItem.year, schoolYearItem.semester @@ -143,10 +139,10 @@ class DutRequestRepository { fun getSubjectFee( accountSession: AccountSession, schoolYearItem: SchoolYearItem - ): ArrayList? { + ): ArrayList? { return try { - if (Account.isLoggedIn(accountSession.toAccountSessionSuper())) { - Account.fetchSubjectFee( + if (Accounts.isLoggedIn(accountSession.toAccountSessionSuper())) { + Accounts.fetchSubjectFee( accountSession.toAccountSessionSuper(), schoolYearItem.year, schoolYearItem.semester @@ -160,10 +156,10 @@ class DutRequestRepository { fun getAccountInformation( accountSession: AccountSession - ): AccountInformation? { + ): AccountInformation.StudentInformation? { return try { - if (Account.isLoggedIn(accountSession.toAccountSessionSuper())) { - Account.fetchAccountInformation(accountSession.toAccountSessionSuper()) + if (Accounts.isLoggedIn(accountSession.toAccountSessionSuper())) { + Accounts.fetchStudentInformation(accountSession.toAccountSessionSuper()) } else null } catch (ex: Exception) { @@ -174,10 +170,10 @@ class DutRequestRepository { fun getAccountTrainingStatus( accountSession: AccountSession - ): AccountTrainingStatus? { + ): TrainingStatus? { return try { - if (Account.isLoggedIn(accountSession.toAccountSessionSuper())) { - Account.fetchAccountTrainingStatus(accountSession.toAccountSessionSuper()) + if (Accounts.isLoggedIn(accountSession.toAccountSessionSuper())) { + Accounts.fetchTrainingStatus(accountSession.toAccountSessionSuper()) } else null } catch (ex: Exception) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/repository/FileModuleRepository.kt b/app/src/main/java/io/zoemeow/dutschedule/repository/FileModuleRepository.kt index 13b800b..d3183d1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/repository/FileModuleRepository.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/repository/FileModuleRepository.kt @@ -3,8 +3,8 @@ package io.zoemeow.dutschedule.repository import android.content.Context import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem -import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation +import io.dutwrapper.dutwrapper.Utils import io.zoemeow.dutschedule.model.NotificationHistory import io.zoemeow.dutschedule.model.account.AccountSession import io.zoemeow.dutschedule.model.news.NewsGlobalItem @@ -13,6 +13,7 @@ import io.zoemeow.dutschedule.model.news.NewsSubjectItem import io.zoemeow.dutschedule.model.settings.AppSettings import java.io.File +@Suppress("PrivatePropertyName", "SpellCheckingInspection") class FileModuleRepository( context: Context ) { @@ -201,14 +202,14 @@ class FileModuleRepository( file.writeText(Gson().toJson(data)) } - fun getAccountSubjectScheduleCache(): ArrayList { + fun getAccountSubjectScheduleCache(): ArrayList { val file = File(PATH_ACCOUNT_SUBJECTSCHEDULE_CACHE) try { file.bufferedReader().apply { val text = this.use { it.readText() } - val objItem = Gson().fromJson>( + val objItem = Gson().fromJson>( text, - (object : TypeToken>() {}.type) + (object : TypeToken>() {}.type) ) this.close() return objItem @@ -219,7 +220,7 @@ class FileModuleRepository( } } - fun saveAccountSubjectScheduleCache(data: ArrayList) { + fun saveAccountSubjectScheduleCache(data: ArrayList) { val file = File(PATH_ACCOUNT_SUBJECTSCHEDULE_CACHE) file.writeText(Gson().toJson(data)) } @@ -242,7 +243,7 @@ class FileModuleRepository( } } - fun saveCurrentSchoolYearCache(data: DutSchoolYearItem?, lastRequest: Long) { + fun saveCurrentSchoolYearCache(data: Utils.DutSchoolYearItem?, lastRequest: Long) { val file = File(PATH_SCHOOLYEAR_CACHE) val dataMap = mapOf("data" to Gson().toJson(data), "lastrequest" to lastRequest) file.writeText(Gson().toJson(dataMap)) diff --git a/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt b/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt index 52c231d..9bf2f78 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/service/NewsBackgroundUpdateService.kt @@ -7,10 +7,8 @@ import android.content.Intent import android.os.Build import android.util.Log import com.google.gson.Gson -import io.dutwrapper.dutwrapper.model.enums.LessonStatus -import io.dutwrapper.dutwrapper.model.enums.NewsType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem -import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem +import io.dutwrapper.dutwrapper.News +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.activity.PermissionsActivity @@ -318,7 +316,7 @@ class NewsBackgroundUpdateService : BaseService( val anyMatch = newsList.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date && newsSourceItem.title == newsTargetItem.title - && newsSourceItem.contentString == newsTargetItem.contentString + && newsSourceItem.content == newsTargetItem.content } val anyNeedUpdated = newsList.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date @@ -437,7 +435,7 @@ class NewsBackgroundUpdateService : BaseService( val anyMatch = newsList.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date && newsSourceItem.title == newsTargetItem.title - && newsSourceItem.contentString == newsTargetItem.contentString + && newsSourceItem.content == newsTargetItem.content } val anyNeedUpdated = newsList.any { newsSourceItem -> newsSourceItem.date == newsTargetItem.date @@ -541,14 +539,14 @@ class NewsBackgroundUpdateService : BaseService( private fun notifyNewsGlobal( context: Context, - newsItem: NewsGlobalItem + newsItem: NewsItem ) { // Add to notification list addToNotificationList( title = newsItem.title, - description = newsItem.contentString, + description = newsItem.content, newsDate = newsItem.date, - type = NewsType.Global, + type = News.NewsType.Global, jsonData = Gson().toJson(newsItem) ) @@ -565,7 +563,7 @@ class NewsBackgroundUpdateService : BaseService( private fun notifyNewsSubject( context: Context, - newsItem: NewsSubjectItem + newsItem: News.NewsSubjectItem ) { if (settings.newsBackgroundParseNewsSubject) { // Affected classrooms @@ -591,7 +589,7 @@ class NewsBackgroundUpdateService : BaseService( // Title will make announcement about lecturer and subjects val notifyTitle = when (newsItem.lessonStatus) { - LessonStatus.Leaving -> { + News.LessonStatus.Leaving -> { context.getString( R.string.service_newsbackgroundservice_newssubject_title_noannouncement, context.getString(R.string.service_newsbackgroundservice_newssubject_title_noannouncement_leaving), @@ -599,7 +597,7 @@ class NewsBackgroundUpdateService : BaseService( affectedClassrooms ) } - LessonStatus.MakeUp -> { + News.LessonStatus.MakeUpLesson -> { context.getString( R.string.service_newsbackgroundservice_newssubject_title_noannouncement, context.getString(R.string.service_newsbackgroundservice_newssubject_title_noannouncement_makeup), @@ -619,8 +617,8 @@ class NewsBackgroundUpdateService : BaseService( val notifyContentList = arrayListOf() // Date and lessons if ( - newsItem.lessonStatus == LessonStatus.Leaving || - newsItem.lessonStatus == LessonStatus.MakeUp + newsItem.lessonStatus == News.LessonStatus.Leaving || + newsItem.lessonStatus == News.LessonStatus.MakeUpLesson ) { // Date & lessons notifyContentList.add( @@ -631,7 +629,7 @@ class NewsBackgroundUpdateService : BaseService( ) ) // Make-up room - if (newsItem.lessonStatus == LessonStatus.MakeUp) { + if (newsItem.lessonStatus == News.LessonStatus.MakeUpLesson) { // Make up in room notifyContentList.add( context.getString( @@ -641,7 +639,7 @@ class NewsBackgroundUpdateService : BaseService( ) } } else { - notifyContentList.add(newsItem.contentString) + notifyContentList.add(newsItem.content) } // TODO: Add to notification list - Disabled due to not excluding here @@ -678,7 +676,7 @@ class NewsBackgroundUpdateService : BaseService( channelId = "notification.id.news.subject", newsMD5 = "${newsItem.date}_${newsItem.title}".calcMD5(), newsTitle = newsItem.title, - newsDescription = newsItem.contentString, + newsDescription = newsItem.content, jsonData = Gson().toJson(newsItem) ) } @@ -688,7 +686,7 @@ class NewsBackgroundUpdateService : BaseService( title: String, description: String, newsDate: Long, - type: NewsType, + type: News.NewsType, jsonData: String ) { // Load notification history @@ -699,15 +697,15 @@ class NewsBackgroundUpdateService : BaseService( title = title, description = description, tag = when (type) { - NewsType.Global -> 1 - NewsType.Subject -> 2 + News.NewsType.Global -> 1 + News.NewsType.Subject -> 2 else -> 0 }, timestamp = newsDate, parameters = mapOf( "type" to when (type) { - NewsType.Global -> NewsActivity.NEWSTYPE_NEWSGLOBAL - NewsType.Subject -> NewsActivity.NEWSTYPE_NEWSSUBJECT + News.NewsType.Global -> NewsActivity.NEWSTYPE_NEWSGLOBAL + News.NewsType.Subject -> NewsActivity.NEWSTYPE_NEWSSUBJECT else -> "" }, "data" to jsonData diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectFeeInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectFeeInformation.kt index 5ac52a9..e900cf9 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectFeeInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectFeeInformation.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -15,13 +14,12 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.SubjectFeeItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectFee import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.ui.component.base.Tag @@ -30,7 +28,7 @@ import io.zoemeow.dutschedule.ui.component.base.Tag @Composable fun AccountSubjectFeeInformation( modifier: Modifier = Modifier, - item: SubjectFeeItem, + item: SubjectFee, onClick: (() -> Unit)? = null, opacity: Float = 1f ) { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectInformation.kt index baa2722..c28a22d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectInformation.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.utils.CustomDateUtil @@ -21,7 +21,7 @@ import io.zoemeow.dutschedule.utils.CustomDateUtil @Composable fun SubjectInformation( modifier: Modifier = Modifier, - item: SubjectScheduleItem, + item: SubjectInformation, onClick: (() -> Unit)? = null, opacity: Float = 1f ) { @@ -48,7 +48,7 @@ fun SubjectInformation( text = context.getString( R.string.account_subjectinfo_summary_schinfo, item.lecturer, - item.subjectStudy.scheduleList.joinToString( + item.scheduleStudy.scheduleList.joinToString( separator = "\n", transform = { schItem -> context.getString( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectMoreInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectMoreInformation.kt index 5e7a596..b258749 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectMoreInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/AccountSubjectMoreInformation.kt @@ -18,7 +18,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.settings.SubjectCode @@ -28,7 +28,7 @@ import io.zoemeow.dutschedule.utils.CustomDateUtil @Composable fun AccountSubjectMoreInformation( context: Context, - item: SubjectScheduleItem? = null, + item: SubjectInformation? = null, isVisible: Boolean = false, onAddToFilterRequested: ((SubjectCode) -> Unit)? = null, dismissClicked: (() -> Unit)? = null, @@ -60,7 +60,7 @@ fun AccountSubjectMoreInformation( CustomText(context.getString( R.string.account_subjectinfo_data_schedulestudy_dayofweek, item?.let { - it.subjectStudy.scheduleList.joinToString( + it.scheduleStudy.scheduleList.joinToString( separator = "; ", transform = { item1 -> "${CustomDateUtil.dayOfWeekInString(context, item1.dayOfWeek + 1)},${item1.lesson.start}-${item1.lesson.end},${item1.room}" @@ -71,7 +71,7 @@ fun AccountSubjectMoreInformation( CustomText(context.getString( R.string.account_subjectinfo_data_schedulestudy_weekrange, item?.let { - it.subjectStudy.weekList.joinToString( + it.scheduleStudy.weekAffected.joinToString( separator = "; ", transform = { item1 -> "${item1.start}-${item1.end}" @@ -89,20 +89,20 @@ fun AccountSubjectMoreInformation( if (item != null) { CustomText(context.getString( R.string.account_subjectinfo_data_scheduleexam_group, - item.subjectExam.group, - if (item.subjectExam.isGlobal) context.getString(R.string.account_subjectinfo_data_scheduleexam_groupglobal) else "" + item.scheduleExam.group, + if (item.scheduleExam.isGlobal) context.getString(R.string.account_subjectinfo_data_scheduleexam_groupglobal) else "" )) CustomText(context.getString( R.string.account_subjectinfo_data_scheduleexam_date, CustomDateUtil.dateUnixToString( - item.subjectExam.date, + item.scheduleExam.date, "dd/MM/yyyy HH:mm", "GMT+7" ) )) CustomText(context.getString( R.string.account_subjectinfo_data_scheduleexam_room, - item.subjectExam.room + item.scheduleExam.room )) } else { CustomText(context.getString(R.string.account_subjectinfo_data_scheduleexam_noexamdate)) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/SubjectResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/SubjectResult.kt index a0d2f30..5feba03 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/SubjectResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/account/SubjectResult.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.trainingresult.SubjectResult +import io.dutwrapper.dutwrapper.AccountInformation import io.zoemeow.dutschedule.GlobalVariables import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.ui.component.base.Tag @@ -30,7 +30,7 @@ import io.zoemeow.dutschedule.ui.component.base.Tag @OptIn(ExperimentalLayoutApi::class) @Composable fun SubjectResult( - subjectResult: SubjectResult, + subjectResult: AccountInformation.SubjectResult, modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, opacity: Float = 1f @@ -71,7 +71,7 @@ fun SubjectResult( } Spacer(modifier = Modifier.size(3.dp)) Text( - "${subjectResult.resultT10} / ${subjectResult.resultT4} / ${subjectResult.resultByCharacter}", + "${subjectResult.resultT10} / ${subjectResult.resultT4} / ${subjectResult.resultByChar}", style = MaterialTheme.typography.bodyMedium ) } @@ -83,7 +83,7 @@ fun SubjectResult( @Composable private fun SubjectResultPreview() { SubjectResult( - SubjectResult( + AccountInformation.SubjectResult( 64, "2023-2024", false, "1023623.2220.19.14", "Toán ứng dụng Công nghệ thông tin", 2.0, "Point formula", 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, "F", true ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt index f4c4707..c52e936 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/DateAndTimeSummaryItem.kt @@ -16,7 +16,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem +import io.dutwrapper.dutwrapper.Utils import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.CustomClock import io.zoemeow.dutschedule.utils.CustomDateUtil @@ -27,7 +27,7 @@ fun DateAndTimeSummaryItem( context: Context, padding: PaddingValues = PaddingValues(), isLoading: Boolean = false, - currentSchoolWeek: DutSchoolYearItem? = null, + currentSchoolWeek: Utils.DutSchoolYearItem? = null, opacity: Float = 1.0f ) { val dateTimeString = remember { mutableStateOf("") } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt index c0c4e48..612ee5a 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/main/LessonsTodaySummaryItem.kt @@ -8,7 +8,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.CustomClock import java.util.Locale @@ -18,7 +18,7 @@ fun LessonTodaySummaryItem( context: Context, hasLoggedIn: Boolean = false, isLoading: Boolean = false, - affectedList: List = listOf(), + affectedList: List = listOf(), padding: PaddingValues = PaddingValues(), clicked: () -> Unit, opacity: Float = 1.0f @@ -43,7 +43,7 @@ fun LessonTodaySummaryItem( Locale.ROOT, "%s (%s)", item.name, - item.subjectStudy.scheduleList.filter { it.lesson.end >= currentLesson.lesson }.joinToString( + item.scheduleStudy.scheduleList.filter { it.lesson.end >= currentLesson.lesson }.joinToString( separator = ", ", transform = { String.format(Locale.ROOT, "%d-%d", it.lesson.start, it.lesson.end) } ) diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt index 13806d1..0310517 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsDetailScreen.kt @@ -24,23 +24,21 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.enums.LessonStatus -import io.dutwrapper.dutwrapper.model.enums.NewsType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem -import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem +import io.dutwrapper.dutwrapper.News +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.utils.CustomDateUtil @Composable fun NewsDetailScreen( context: Context, - newsItem: NewsGlobalItem, - newsType: NewsType, + newsItem: NewsItem, + newsType: News.NewsType, padding: PaddingValues = PaddingValues(0.dp), linkClicked: ((String) -> Unit)? = null ) { when (newsType) { - NewsType.Global -> { + News.NewsType.Global -> { NewsDetailBody_NewsGlobal( context = context, padding = padding, @@ -48,11 +46,11 @@ fun NewsDetailScreen( linkClicked = linkClicked ) } - NewsType.Subject -> { + News.NewsType.Subject -> { NewsDetailBody_NewsSubject( context = context, padding = padding, - newsItem = newsItem as NewsSubjectItem, + newsItem = newsItem as News.NewsSubjectItem, linkClicked = linkClicked ) } @@ -63,7 +61,7 @@ fun NewsDetailScreen( private fun NewsDetailBody_NewsGlobal( context: Context, padding: PaddingValues, - newsItem: NewsGlobalItem, + newsItem: NewsItem, linkClicked: ((String) -> Unit)? = null ) { Box( @@ -101,20 +99,20 @@ private fun NewsDetailBody_NewsGlobal( modifier = Modifier.padding(bottom = 10.dp) ) val annotatedString = buildAnnotatedString { - if (newsItem.contentString != null) { + if (newsItem.content != null) { // Parse all string to annotated string. - append(newsItem.contentString) + append(newsItem.content) // Adjust color for annotated string to follow system mode. addStyle( style = SpanStyle(color = MaterialTheme.colorScheme.inverseSurface), start = 0, - end = newsItem.contentString.length + end = newsItem.content.length ) // Adjust for detected link. - newsItem.links?.forEach { + newsItem.resources?.forEach { addStringAnnotation( tag = it.position!!.toString(), - annotation = it.url!!, + annotation = it.content!!, start = it.position, end = it.position + it.text!!.length ) @@ -132,7 +130,7 @@ private fun NewsDetailBody_NewsGlobal( style = MaterialTheme.typography.bodyLarge, onClick = { try { - newsItem.links?.forEach { item -> + newsItem.resources?.forEach { item -> annotatedString .getStringAnnotations(item.position!!.toString(), it, it) .firstOrNull() @@ -166,7 +164,7 @@ private fun NewsDetailBody_NewsGlobal( private fun NewsDetailBody_NewsSubject( context: Context, padding: PaddingValues, - newsItem: NewsSubjectItem, + newsItem: News.NewsSubjectItem, linkClicked: ((String) -> Unit)? = null ) { val optionsScrollState = rememberScrollState() @@ -242,16 +240,16 @@ private fun NewsDetailBody_NewsSubject( ) // Affecting lessons, hour, room. if (arrayListOf( - LessonStatus.Leaving, - LessonStatus.MakeUp + News.LessonStatus.Leaving, + News.LessonStatus.MakeUpLesson ).contains(newsItem.lessonStatus) ) { Text( text = context.getString( R.string.news_detail_newssubject_subjectstatus, when (newsItem.lessonStatus) { - LessonStatus.Leaving -> context.getString(R.string.news_detail_newssubject_subjectstatus_leaving) - LessonStatus.MakeUp -> context.getString(R.string.news_detail_newssubject_subjectstatus_makeup) + News.LessonStatus.Leaving -> context.getString(R.string.news_detail_newssubject_subjectstatus_leaving) + News.LessonStatus.MakeUpLesson -> context.getString(R.string.news_detail_newssubject_subjectstatus_makeup) else -> context.getString(R.string.news_detail_newssubject_subjectstatus_unknown) } ), @@ -273,7 +271,7 @@ private fun NewsDetailBody_NewsSubject( ), style = MaterialTheme.typography.bodyLarge, ) - if (newsItem.lessonStatus == LessonStatus.MakeUp) { + if (newsItem.lessonStatus == News.LessonStatus.MakeUpLesson) { Spacer(modifier = Modifier.size(5.dp)) Text( text = context.getString( @@ -290,20 +288,20 @@ private fun NewsDetailBody_NewsSubject( ) } val annotatedString = buildAnnotatedString { - if (newsItem.contentString != null) { + if (newsItem.content != null) { // Parse all string to annotated string. - append(newsItem.contentString) + append(newsItem.content) // Adjust color for annotated string to follow system mode. addStyle( style = SpanStyle(color = MaterialTheme.colorScheme.inverseSurface), start = 0, - end = newsItem.contentString.length + end = newsItem.content.length ) // Adjust for detected link. - newsItem.links?.forEach { + newsItem.resources?.forEach { addStringAnnotation( tag = it.position!!.toString(), - annotation = it.url!!, + annotation = it.content!!, start = it.position, end = it.position + it.text!!.length ) @@ -321,7 +319,7 @@ private fun NewsDetailBody_NewsSubject( style = MaterialTheme.typography.bodyLarge, onClick = { try { - newsItem.links?.forEach { item -> + newsItem.resources?.forEach { item -> annotatedString .getStringAnnotations( item.position!!.toString(), diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsListPage.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsListPage.kt index 9502dd6..1ed890e 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsListPage.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsListPage.kt @@ -19,17 +19,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.utils.CustomDateUtil import io.zoemeow.dutschedule.utils.endOfListReached @Composable fun NewsListPage( - newsList: List = listOf(), + newsList: List = listOf(), processState: ProcessState = ProcessState.NotRunYet, endOfListReached: (() -> Unit)? = null, - itemClicked: ((NewsGlobalItem) -> Unit)? = null, + itemClicked: ((NewsItem) -> Unit)? = null, lazyListState: LazyListState = rememberLazyListState(), opacity: Float = 1f ) { @@ -66,7 +66,7 @@ fun NewsListPage( items (newsGroup.value) { newsItem -> NewsListItem( title = newsItem.title ?: "", - description = newsItem.contentString ?: "", + description = newsItem.content ?: "", opacity = opacity, onClick = { itemClicked?.let { it(newsItem) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt index 2ad882b..b52c955 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsPopup.kt @@ -1,18 +1,11 @@ package io.zoemeow.dutschedule.ui.component.news import android.content.Context -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.waterfall import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Clear @@ -24,7 +17,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults @@ -37,9 +29,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import io.dutwrapper.dutwrapper.model.enums.NewsType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem -import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem +import io.dutwrapper.dutwrapper.News +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.AppearanceState @@ -130,8 +121,8 @@ fun NewsPopup( NewsDetailScreen( context = context, padding = paddingValues, - newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), - newsType = NewsType.Global, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), + newsType = News.NewsType.Global, linkClicked = { link -> onLinkClicked?.let { it(link) } } @@ -141,8 +132,8 @@ fun NewsPopup( NewsDetailScreen( context = context, padding = paddingValues, - newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsGlobalItem, - newsType = NewsType.Subject, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsItem, + newsType = News.NewsType.Subject, linkClicked = { link -> onLinkClicked?.let { it(link) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchOptionAndHistory.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchOptionAndHistory.kt index afefd89..e94723c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchOptionAndHistory.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchOptionAndHistory.kt @@ -35,8 +35,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.enums.NewsSearchType -import io.dutwrapper.dutwrapper.model.enums.NewsType +import io.dutwrapper.dutwrapper.News import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.news.NewsSearchHistory @@ -49,9 +48,9 @@ fun NewsSearchOptionAndHistory( searchHistory: List, backgroundColor: Color = MaterialTheme.colorScheme.background, query: String = "", - newsMethod: NewsSearchType, - newsType: NewsType, - onSettingsChanged: ((String, NewsSearchType, NewsType) -> Unit)? = null, + newsMethod: News.NewsSearchType, + newsType: News.NewsType, + onSettingsChanged: ((String, News.NewsSearchType, News.NewsType) -> Unit)? = null, onSearchTriggered: (() -> Unit)? = null, onClearHistoryTriggered: (() -> Unit)? = null, onDismiss: (() -> Unit)? = null @@ -78,16 +77,16 @@ fun NewsSearchOptionAndHistory( .fillMaxWidth() .padding(bottom = 5.dp), content = { - NewsSearchType.values().forEachIndexed { index, item -> + News.NewsSearchType.entries.forEachIndexed { index, item -> SegmentedButton( - shape = SegmentedButtonDefaults.itemShape(index = index, count = NewsSearchType.values().size), + shape = SegmentedButtonDefaults.itemShape(index = index, count = News.NewsSearchType.entries.size), onClick = { onSettingsChanged?.let { it(query, item, newsType) } }, selected = newsMethod == item, label = { Text( when (item) { - NewsSearchType.ByTitle -> context.getString(R.string.news_search_searchoption_method_bytitle) - NewsSearchType.ByContent -> context.getString(R.string.news_search_searchoption_method_bycontent) + News.NewsSearchType.ByTitle -> context.getString(R.string.news_search_searchoption_method_bytitle) + News.NewsSearchType.ByContent -> context.getString(R.string.news_search_searchoption_method_bycontent) } ) } @@ -105,9 +104,9 @@ fun NewsSearchOptionAndHistory( .fillMaxWidth() .padding(bottom = 5.dp), content = { - NewsType.values().forEachIndexed { index, item -> + News.NewsType.entries.forEachIndexed { index, item -> SegmentedButton( - shape = SegmentedButtonDefaults.itemShape(index = index, count = NewsType.values().size), + shape = SegmentedButtonDefaults.itemShape(index = index, count = News.NewsType.entries.size), onClick = { onSettingsChanged?.let { it(query, newsMethod, item) @@ -117,8 +116,8 @@ fun NewsSearchOptionAndHistory( label = { Text( when (item) { - NewsType.Subject -> context.getString(R.string.news_search_searchoption_type_bysubject) - NewsType.Global -> context.getString(R.string.news_search_searchoption_type_byglobal) + News.NewsType.Subject -> context.getString(R.string.news_search_searchoption_type_bysubject) + News.NewsType.Global -> context.getString(R.string.news_search_searchoption_type_byglobal) } ) } @@ -161,12 +160,12 @@ fun NewsSearchOptionAndHistory( Text(context.getString( R.string.news_search_searchoption_history_data, when (queryItem.newsMethod) { - NewsSearchType.ByTitle -> context.getString(R.string.news_search_searchoption_method_bytitle) - NewsSearchType.ByContent -> context.getString(R.string.news_search_searchoption_method_bycontent) + News.NewsSearchType.ByTitle -> context.getString(R.string.news_search_searchoption_method_bytitle) + News.NewsSearchType.ByContent -> context.getString(R.string.news_search_searchoption_method_bycontent) }, when (queryItem.newsType) { - NewsType.Subject -> context.getString(R.string.news_search_searchoption_type_bysubject) - NewsType.Global -> context.getString(R.string.news_search_searchoption_type_byglobal) + News.NewsType.Subject -> context.getString(R.string.news_search_searchoption_type_bysubject) + News.NewsType.Global -> context.getString(R.string.news_search_searchoption_type_byglobal) } )) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchResult.kt index 84a5ee5..ce8b1b6 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/news/NewsSearchResult.kt @@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.utils.endOfListReached @@ -26,12 +26,12 @@ import io.zoemeow.dutschedule.utils.endOfListReached fun NewsSearchResult( context: Context, modifier: Modifier = Modifier, - newsList: List, + newsList: List, lazyListState: LazyListState, opacity: Float = 1f, processState: ProcessState, onEndOfList: (() -> Unit)? = null, - onItemClicked: ((NewsGlobalItem) -> Unit)? = null + onItemClicked: ((NewsItem) -> Unit)? = null ) { Column(modifier = modifier) { if (processState == ProcessState.Running) { @@ -86,7 +86,7 @@ fun NewsSearchResult( items(newsList) { item -> NewsListItem( title = item.title, - description = item.contentString, + description = item.content, dateTime = item.date, opacity = opacity, onClick = { diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt index fb8354a..bc943c1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogSchoolYearSettings.kt @@ -39,7 +39,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.Locale -@OptIn(ExperimentalMaterial3Api::class) @Composable fun DialogSchoolYearSettings( context: Context, diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt index f823d02..34616e0 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/SubjectInformation.kt @@ -33,7 +33,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import io.dutwrapper.dutwrapper.model.accounts.SubjectScheduleItem +import io.dutwrapper.dutwrapper.AccountInformation.SubjectInformation import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState @@ -51,7 +51,7 @@ fun Activity_Account_SubjectInformation( onMessageReceived: (String, Boolean, String?, (() -> Unit)?) -> Unit, // (msg, forceDismissBefore, actionText, action) onBack: () -> Unit ) { - val subjectScheduleItem: MutableState = remember { mutableStateOf(null) } + val subjectScheduleItem: MutableState = remember { mutableStateOf(null) } val subjectDetailVisible = remember { mutableStateOf(false) } Scaffold( diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt index 2961f27..73e4dc0 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingResult.kt @@ -242,11 +242,11 @@ fun Activity_Account_TrainingResult( content = { CheckboxOption( title = context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_havepe), - isChecked = it.graduateStatus?.hasSigGDTC == true + isChecked = it.graduateStatus?.hasSigPhysicalEducation == true ) CheckboxOption( title = context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_havende), - isChecked = it.graduateStatus?.hasSigGDQP == true + isChecked = it.graduateStatus?.hasSigNationalDefenseEducation == true ) CheckboxOption( title = context.getString(R.string.account_trainingstatus_graduatebox_certandgraduateresult_haveenglish), diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt index 212ad7c..1544e15 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/account/TrainingSubjectResult.kt @@ -61,7 +61,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import io.dutwrapper.dutwrapper.model.accounts.trainingresult.SubjectResult +import io.dutwrapper.dutwrapper.AccountInformation import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.model.AppearanceState import io.zoemeow.dutschedule.model.ProcessState @@ -98,9 +98,9 @@ fun Activity_Account_TrainingSubjectResult( val sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ) - val selectedSubject = remember { mutableStateOf(null) } + val selectedSubject = remember { mutableStateOf(null) } - fun subjectResultToMap(item: SubjectResult): Map { + fun subjectResultToMap(item: AccountInformation.SubjectResult): Map { return mapOf( context.getString(R.string.account_trainingstatus_subjectresult_schoolyear) to "${item.schoolYear ?: context.getString(R.string.data_unknown)}${ if (item.isExtendedSemester) " (${context.getString(R.string.account_trainingstatus_subjectresult_schoolyear_insummer)})" else "" }", context.getString(R.string.account_trainingstatus_subjectresult_subjectcode) to (item.id ?: context.getString(R.string.data_unknown)), @@ -123,7 +123,7 @@ fun Activity_Account_TrainingSubjectResult( item.resultT10 ) else context.getString(R.string.data_noscore), if (item.resultT4 != null) String.format(Locale.ROOT, "%.2f", item.resultT4) else context.getString(R.string.data_noscore), - if (item.resultByCharacter.isNullOrEmpty()) "(${context.getString(R.string.data_noscore)})" else item.resultByCharacter + if (item.resultByChar.isNullOrEmpty()) "(${context.getString(R.string.data_noscore)})" else item.resultByChar ) ) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt index acd46ef..fa0e463 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/main/MainViewDashboard.kt @@ -232,8 +232,8 @@ fun Activity_MainView_Dashboard_Body( } }, affectedList = mainViewModel.accountSession.subjectSchedule.data.filter { subSch -> - subSch.subjectStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && - subSch.subjectStudy.scheduleList.any { schItem -> + subSch.scheduleStudy.scheduleList.any { schItem -> schItem.dayOfWeek + 1 == CustomDateUtil.getCurrentDayOfWeek() } && + subSch.scheduleStudy.scheduleList.any { schItem -> schItem.lesson.end >= CustomClock.getCurrent() .toDUTLesson2().lesson } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt index 9106c72..d5bfb94 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/MainView.kt @@ -45,7 +45,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.google.gson.Gson -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.AppearanceState @@ -254,7 +254,7 @@ fun Activity_News( 1 -> { (NewsListPage( - newsList = mainViewModel.newsInstance.newsSubject.data.toList() as List, + newsList = mainViewModel.newsInstance.newsSubject.data.toList() as List, processState = mainViewModel.newsInstance.newsSubject.processState.value, opacity = appearanceState.componentOpacity, itemClicked = { newsItem -> diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt index 0a671aa..17c351c 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsDetail.kt @@ -26,9 +26,9 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.dp import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import io.dutwrapper.dutwrapper.model.enums.NewsType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem -import io.dutwrapper.dutwrapper.model.news.NewsSubjectItem +import io.dutwrapper.dutwrapper.News +import io.dutwrapper.dutwrapper.News.NewsItem +import io.dutwrapper.dutwrapper.News.NewsSubjectItem import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.AppearanceState @@ -106,8 +106,8 @@ fun Activity_News_NewsDetail( NewsDetailScreen( context = context, padding = it, - newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), - newsType = NewsType.Global, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type), + newsType = News.NewsType.Global, linkClicked = { link -> onLinkClicked?.let { it(link) } } @@ -117,8 +117,8 @@ fun Activity_News_NewsDetail( NewsDetailScreen( context = context, padding = it, - newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsGlobalItem, - newsType = NewsType.Subject, + newsItem = Gson().fromJson(newsData, object : TypeToken() {}.type) as NewsItem, + newsType = News.NewsType.Subject, linkClicked = { link -> onLinkClicked?.let { it(link) } } diff --git a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt index f0e2924..40d05a1 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/ui/view/news/NewsSearch.kt @@ -38,7 +38,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.google.gson.Gson -import io.dutwrapper.dutwrapper.model.enums.NewsType +import io.dutwrapper.dutwrapper.News import io.zoemeow.dutschedule.R import io.zoemeow.dutschedule.activity.NewsActivity import io.zoemeow.dutschedule.model.AppearanceState @@ -175,7 +175,7 @@ fun Activity_News_NewsSearch( NewsActivity::class.java ).also { it.action = NewsActivity.INTENT_NEWSDETAILACTIVITY - it.putExtra("type", if (newsSearchViewModel.type.value == NewsType.Subject) NewsActivity.NEWSTYPE_NEWSSUBJECT else NewsActivity.NEWSTYPE_NEWSGLOBAL) + it.putExtra("type", if (newsSearchViewModel.type.value == News.NewsType.Subject) NewsActivity.NEWSTYPE_NEWSSUBJECT else NewsActivity.NEWSTYPE_NEWSGLOBAL) it.putExtra("data", Gson().toJson(item)) }) } diff --git a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt index b41d2cb..20b8dc4 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/MainViewModel.kt @@ -9,7 +9,6 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import dagger.hilt.android.lifecycle.HiltViewModel import io.dutwrapper.dutwrapper.Utils -import io.dutwrapper.dutwrapper.model.utils.DutSchoolYearItem import io.zoemeow.dutschedule.model.NotificationHistory import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.VariableState @@ -58,7 +57,7 @@ class MainViewModel @Inject constructor( } ) - val currentSchoolYearWeek = VariableState( + val currentSchoolYearWeek = VariableState( data = mutableStateOf(null) ) @@ -173,7 +172,7 @@ class MainViewModel @Inject constructor( try { currentSchoolYearWeek.data.value = Gson().fromJson( it["data"] ?: "", - (object : TypeToken() {}.type) + (object : TypeToken() {}.type) ) currentSchoolYearWeek.lastRequest.longValue = (it["lastrequest"] ?: "0").toLong() } catch (_: Exception) { } diff --git a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/NewsSearchViewModel.kt b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/NewsSearchViewModel.kt index 14eb78d..011372d 100644 --- a/app/src/main/java/io/zoemeow/dutschedule/viewmodel/NewsSearchViewModel.kt +++ b/app/src/main/java/io/zoemeow/dutschedule/viewmodel/NewsSearchViewModel.kt @@ -6,9 +6,8 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import dagger.hilt.android.lifecycle.HiltViewModel -import io.dutwrapper.dutwrapper.model.enums.NewsSearchType -import io.dutwrapper.dutwrapper.model.enums.NewsType -import io.dutwrapper.dutwrapper.model.news.NewsGlobalItem +import io.dutwrapper.dutwrapper.News +import io.dutwrapper.dutwrapper.News.NewsItem import io.zoemeow.dutschedule.model.ProcessState import io.zoemeow.dutschedule.model.news.NewsSearchHistory import io.zoemeow.dutschedule.repository.DutRequestRepository @@ -28,10 +27,10 @@ class NewsSearchViewModel @Inject constructor( val query = mutableStateOf("") // Search method - val method = mutableStateOf(NewsSearchType.ByTitle) + val method = mutableStateOf(News.NewsSearchType.ByTitle) // News search type - val type = mutableStateOf(NewsType.Global) + val type = mutableStateOf(News.NewsType.Global) // // @@ -39,7 +38,7 @@ class NewsSearchViewModel @Inject constructor( val progress = mutableStateOf(ProcessState.NotRunYet) // News result - val newsList = mutableStateListOf() + val newsList = mutableStateListOf() // News page private val newsPage = mutableIntStateOf(1) @@ -76,7 +75,7 @@ class NewsSearchViewModel @Inject constructor( if (startOver) { newsList.clear() } - if (type.value == NewsType.Subject) { + if (type.value == News.NewsType.Subject) { newsList.addAll( dutRequestRepository.getNewsSubject( if (startOver) 1 else newsPage.intValue, From 2490cd63bb3550d114085cc3b4cb2adb36c0bccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Qu=E1=BA=BF=20T=C3=B9ng?= <47247560+ZoeMeow1027@users.noreply.github.com> Date: Fri, 2 Aug 2024 20:39:56 +0700 Subject: [PATCH 09/12] Update wallpaper settings and more... - Wallpaper settings and background opacity/component opacity will be grouped into one. - Fix a issue cause account session remains even after logged out. --- .idea/other.xml | 11 -- CHANGELOG.md | 18 +- app/build.gradle | 5 +- .../dutschedule/activity/BaseActivity.kt | 8 - .../dutschedule/activity/SettingsActivity.kt | 70 +++++++ .../dutschedule/model/AppearanceState.kt | 16 +- .../dutschedule/model/settings/AppSettings.kt | 21 ++ .../settings/DialogAppBackgroundSettings.kt | 100 ---------- .../ui/component/settings/SliderWithValue.kt | 120 ++++++++++++ .../ui/view/settings/ExperimentSettings.kt | 17 -- .../dutschedule/ui/view/settings/MainView.kt | 59 +----- .../ui/view/settings/WallpaperSettings.kt | 185 ++++++++++++++++++ .../dutschedule/viewmodel/MainViewModel.kt | 1 + app/src/main/res/values-vi/strings.xml | 29 +-- app/src/main/res/values/strings.xml | 29 +-- 15 files changed, 467 insertions(+), 222 deletions(-) delete mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/DialogAppBackgroundSettings.kt create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/component/settings/SliderWithValue.kt create mode 100644 app/src/main/java/io/zoemeow/dutschedule/ui/view/settings/WallpaperSettings.kt diff --git a/.idea/other.xml b/.idea/other.xml index 0d3a1fb..4604c44 100644 --- a/.idea/other.xml +++ b/.idea/other.xml @@ -179,17 +179,6 @@