get() = _windows
17 |
18 | override fun startPage(key: String) {
19 | val page = Pages[key] ?: EmptyPage
20 | _windows.add(
21 | WindowPage(
22 | page = page,
23 | )
24 | )
25 | }
26 |
27 | override fun back() {
28 | val item = _windows.last()
29 | item.exit()
30 | _windows.removeLast()
31 | }
32 |
33 |
34 | fun close(windowPage: WindowPage){
35 | windowPage.exit()
36 | _windows.remove(windowPage)
37 | }
38 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # 青科助手
8 |
9 | ✨ 一款集教务查询和课表管理于一体的移动端软件 ✨
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ### 简介 📃
22 |
23 | - Qust Helper 是一款移动端教务系统客户端,旨在为师生提供一个便捷的教务访问方式,随时随地查看课程安排、考试成绩、教务通知等信息。
24 | - Qust Helper 还是一款优秀的课表软件,能够轻松从教务导入课程信息,自动生成个性化的课表。
25 | - 基于全新的 Jetpack Compose 架构,拥有更加出色的用户体验。*后续可能支持多平台*。
26 |
27 | ### 应用截图📸
28 |
29 |   
30 |
31 |
32 | ### 功能实现 💻
33 |
34 | - [x] 教务课表导入
35 | - [x] 成绩查询
36 | - [x] 学业情况查询
37 | - [x] 考试查询
38 | - [x] 教务通知推送
39 | - [x] 成绩推送
40 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/ui/activity/PageActivity.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.activity
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import androidx.activity.ComponentActivity
7 | import androidx.activity.compose.setContent
8 | import com.qust.helper.data.Pages
9 | import com.qust.helper.ui.page.BasePage
10 | import com.qust.helper.ui.page.EmptyPage
11 |
12 | class PageActivity: ComponentActivity() {
13 |
14 | companion object {
15 | fun startActivity(context: Context, page: String){
16 | context.startActivity(Intent(context, PageActivity::class.java).putExtra("page", page))
17 | }
18 | }
19 |
20 | lateinit var page: BasePage<*>
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | val page = Pages[intent.getStringExtra("page")]
25 | if(page == null) {
26 | this.page = EmptyPage
27 | }else{
28 | this.page = page
29 | }
30 |
31 | setContent { this.page.ComposePage() }
32 | }
33 | }
--------------------------------------------------------------------------------
/composeApp/src/desktopMain/kotlin/com/qust/helper/utils/SettingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import com.qust.helper.platform.utils.SettingUtilsImpl
4 |
5 | class SettingImpl : SettingUtilsImpl() {
6 |
7 | override fun getString(key: String, defValue: String) = defValue
8 | override fun getInt(key: String, defValue: Int) = defValue
9 | override fun getBoolean(key: String, defValue: Boolean) = defValue
10 | override fun getFloat(key: String, defValue: Float) = defValue
11 | override fun getLong(key: String, defValue: Long) = defValue
12 | override fun getStringSet(key: String, defValue: Set): Set? = defValue
13 |
14 | override fun putString(key: String, value: String){ }
15 | override fun putInt(key: String, value: Int) { }
16 | override fun putBoolean(key: String, value: Boolean) { }
17 | override fun putFloat(key: String, value: Float) { }
18 | override fun putLong(key: String, value: Long) { }
19 | override fun putStringSet(key: String, value: Set) { }
20 |
21 | override fun removeKey(key: String){ }
22 | }
23 |
24 | actual fun getSettingUtils(): SettingUtilsImpl = SettingImpl()
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/room/dao/LessonMapper.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.room.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.Query
7 | import androidx.room.Transaction
8 | import androidx.room.Update
9 | import com.qust.helper.room.entity.LessonDao
10 |
11 | @Dao
12 | interface LessonMapper {
13 |
14 | @Query("SELECT * FROM lesson")
15 | fun selectAll(): List
16 |
17 | @Insert
18 | fun insert(lesson: LessonDao): Long?
19 |
20 | @Insert
21 | fun insertAll(lessons: List)
22 |
23 | @Update
24 | fun update(lesson: LessonDao): Int
25 |
26 | @Update
27 | fun updateAll(lessons: List): Int
28 |
29 | @Delete
30 | fun delete(lesson: LessonDao)
31 |
32 | @Delete
33 | fun deleteAll(lessons: List)
34 |
35 |
36 | @Query("DELETE FROM lesson WHERE 1")
37 | fun clearTable()
38 |
39 | @Transaction
40 | open fun mergeLesson(new: List, update: List, delete: List) {
41 | insertAll(new)
42 | assert(updateAll(update) == update.size)
43 | deleteAll(delete)
44 | }
45 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/toast/ToastUIState.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.toast
2 |
3 | import androidx.compose.runtime.Stable
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import kotlinx.coroutines.CancellableContinuation
8 | import kotlinx.coroutines.delay
9 | import kotlinx.coroutines.suspendCancellableCoroutine
10 | import kotlinx.coroutines.sync.Mutex
11 | import kotlinx.coroutines.sync.withLock
12 | import kotlin.coroutines.resume
13 |
14 | @Stable
15 | class ToastUIState {
16 | private val mutex = Mutex()
17 |
18 | var currentData: ToastData? by mutableStateOf(null)
19 | private set
20 |
21 | private var continuation: CancellableContinuation? = null
22 |
23 | suspend fun show(toastData: ToastData): Unit = mutex.withLock {
24 | try {
25 | suspendCancellableCoroutine { continuation ->
26 | this.continuation = continuation
27 | currentData = toastData
28 | }
29 | } finally {
30 | currentData = null
31 | }
32 | }
33 |
34 | suspend fun run() {
35 | delay(2000L)
36 | continuation?.resume(Unit)
37 | }
38 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/components/Texts.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.components
2 |
3 | import androidx.compose.foundation.layout.Row
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material3.Icon
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.vector.ImageVector
12 | import androidx.compose.ui.unit.dp
13 | import com.qust.helper.ui.theme.colorSecondaryText
14 |
15 | @Composable
16 | fun IconText(
17 | text: String,
18 | icon: ImageVector,
19 | modifier: Modifier = Modifier,
20 | iconModifier: Modifier = Modifier
21 | ) {
22 | Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically){
23 | Text(
24 | text = text,
25 | style = MaterialTheme.typography.bodySmall,
26 | modifier = Modifier.padding(start = 8.dp),
27 | color = colorSecondaryText,
28 | )
29 | Icon(imageVector = icon, modifier = iconModifier, contentDescription = null, tint = colorSecondaryText)
30 | }
31 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/model/database/MarkStorage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.database
2 |
3 | import com.qust.helper.entity.eas.Mark
4 | import com.qust.helper.room.AppDataBase
5 | import com.qust.helper.room.entity.MarkDao
6 | import com.qust.helper.room.entity.toMark
7 | import com.qust.helper.room.entity.toMarkDao
8 |
9 | actual fun getMarkStorage(): MarkStorage = MarkStorageImpl()
10 |
11 | class MarkStorageImpl: MarkStorage {
12 |
13 | override fun getMarksByKchId(id: String): List {
14 | return AppDataBase.INSTANCE.markDao().selectByKchId(id).map(MarkDao::toMark)
15 | }
16 |
17 | override fun getMarksByIndex(index: Int): List {
18 | return AppDataBase.INSTANCE.markDao().selectByIndex(index).map(MarkDao::toMark)
19 | }
20 |
21 | override fun updateAll(marks: List){
22 | return AppDataBase.INSTANCE.markDao().updateAll(marks.map(Mark::toMarkDao))
23 | }
24 |
25 | override fun insertAll(marks: List){
26 | return AppDataBase.INSTANCE.markDao().insertAll(marks.map(Mark::toMarkDao))
27 | }
28 |
29 | override fun setRead(id: Int) {
30 | return AppDataBase.INSTANCE.markDao().setRead(id)
31 | }
32 |
33 | override fun clear(index: Int) {
34 | return AppDataBase.INSTANCE.markDao().clear(index)
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Electric.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Electric: ImageVector
10 | get() {
11 | if (_Electric != null) {
12 | return _Electric!!
13 | }
14 | _Electric = ImageVector.Builder(
15 | name = "Electric",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 1024f,
19 | viewportHeight = 1024f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFFFFFFFF))) {
22 | moveTo(735.3f, 102.5f)
23 | lineTo(250.1f, 521f)
24 | lineToRelative(260.1f, 82.9f)
25 | lineToRelative(-203.1f, 317.6f)
26 | lineToRelative(519.1f, -384.4f)
27 | lineToRelative(-272.7f, -80.3f)
28 | }
29 | }.build()
30 |
31 | return _Electric!!
32 | }
33 |
34 | @Suppress("ObjectPropertyName")
35 | private var _Electric: ImageVector? = null
36 |
--------------------------------------------------------------------------------
/composeApp/src/desktopMain/kotlin/com/qust/helper/utils/Logger.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import java.io.PrintWriter
4 | import java.io.StringWriter
5 |
6 |
7 | actual fun getLogger(): AbstractLogger = LoggerImpl()
8 |
9 | class LoggerImpl: AbstractLogger {
10 |
11 | override fun d(msg: String) {
12 | println("[QustHelper]\u001b[36m[D]\u001b[0m $msg")
13 | }
14 |
15 | override fun i(msg: String) {
16 | println("[QustHelper]\u001b[32m[I]\u001b[0m $msg")
17 | }
18 |
19 | override fun w(msg: String) {
20 | println("[QustHelper]\u001b[33m[W]\u001b[0m $msg")
21 | }
22 |
23 | override fun e(msg: String?, e: Throwable?) {
24 | if(e == null){
25 | println("[QustHelper]\u001b[31m[E]\u001b[0m $msg")
26 | }else{
27 | val writer = StringWriter()
28 | e.printStackTrace(PrintWriter(writer))
29 | println("[QustHelper]\u001b[31m[E]\u001b[0m $msg ${e.message}\n $writer")
30 | }
31 | }
32 |
33 | override fun log(tag: String, msg: String?, throwable: Throwable?) {
34 | if(throwable == null){
35 | println("[${tag}]\u001b[31m[E]\u001b[0m $msg")
36 | }else{
37 | val writer = StringWriter()
38 | throwable.printStackTrace(PrintWriter(writer))
39 | println("[QustHelper]\u001b[31m[E]\u001b[0m $msg ${throwable.message}\n $writer")
40 | }
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/ui/page/PageController.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page
2 |
3 | import android.content.Context
4 | import androidx.activity.OnBackPressedDispatcher
5 | import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.remember
8 | import androidx.compose.ui.platform.LocalContext
9 | import com.qust.helper.ui.activity.PageActivity
10 |
11 | class AndroidPageController(
12 | val context: Context,
13 | private val dispatcher: OnBackPressedDispatcher?
14 | ): PageController {
15 |
16 | override fun startPage(key: String) {
17 | PageActivity.startActivity(context, key)
18 | }
19 |
20 | override fun back() {
21 | dispatcher?.onBackPressed()
22 | }
23 |
24 | }
25 |
26 | @Composable
27 | actual fun rememberPageController(): PageController {
28 | val context = LocalContext.current
29 | val backDispatcherOwner = LocalOnBackPressedDispatcherOwner.current
30 |
31 | return remember { AndroidPageController(context, backDispatcherOwner?.onBackPressedDispatcher) }
32 | }
33 |
34 | @Composable
35 | actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
36 | androidx.activity.compose.BackHandler(enabled, onBack)
37 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/App.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper
2 |
3 | import android.app.Application
4 | import android.os.Looper
5 | import android.widget.Toast
6 | import com.qust.helper.room.AppDataBase
7 | import com.qust.helper.utils.UmengUtils
8 | import com.tencent.mmkv.MMKV
9 |
10 |
11 | class App: Application() {
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 |
16 | MMKV.initialize(this)
17 |
18 | AppDataBase.init(this)
19 |
20 | if(!BuildConfig.DEBUG) UmengUtils.init(this)
21 |
22 | Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(this, Thread.getDefaultUncaughtExceptionHandler()))
23 | }
24 |
25 | fun toast(message: String) {
26 | object : Thread() {
27 | override fun run() {
28 | Looper.prepare()
29 | Toast.makeText(this@App, message, Toast.LENGTH_LONG).show()
30 | Looper.loop()
31 | }
32 | }.start()
33 | }
34 | }
35 |
36 | private class ExceptionHandler(val app: App, val handler: Thread.UncaughtExceptionHandler?) : Thread.UncaughtExceptionHandler {
37 | override fun uncaughtException(thread: Thread, throwable: Throwable) {
38 | app.toast("应用发生错误,错误类型:" + throwable.javaClass)
39 | // LogUtil.Log("-------应用异常退出-------", throwable)
40 | // LogUtil.debugLog("-------应用异常退出-------\n")
41 | handler?.uncaughtException(thread, throwable)
42 | }
43 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/model/database/LessonTableModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.database
2 |
3 | import com.qust.helper.entity.lesson.Lesson
4 | import com.qust.helper.room.AppDataBase
5 | import com.qust.helper.room.entity.LessonDao
6 | import com.qust.helper.room.entity.toLesson
7 | import com.qust.helper.room.entity.toLessonDao
8 |
9 | actual fun getLessonTableStorage(): LessonTableStorage = LessonTableStorageImpl()
10 |
11 | class LessonTableStorageImpl: LessonTableStorage {
12 |
13 | override suspend fun getAllLesson(): List {
14 | return AppDataBase.INSTANCE.lessonDao().selectAll().map(LessonDao::toLesson)
15 | }
16 |
17 | override suspend fun saveLesson(lesson: Lesson): Long? {
18 | return AppDataBase.INSTANCE.lessonDao().insert(lesson.toLessonDao())
19 | }
20 |
21 | override suspend fun updateLesson(lesson: Lesson): Boolean {
22 | return AppDataBase.INSTANCE.lessonDao().update(lesson.toLessonDao()) == 1
23 | }
24 |
25 | override suspend fun mergeLesson(new: List, update: List, delete: List): Boolean {
26 | try{
27 | AppDataBase.INSTANCE.lessonDao().mergeLesson(new.map(Lesson::toLessonDao), update.map(Lesson::toLessonDao), delete.map(Lesson::toLessonDao))
28 | return true
29 | }catch(e: Exception){
30 | return false
31 | }
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/utils/UmengUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import android.content.Context
4 | import com.qust.helper.BuildConfig
5 | import com.qust.helper.data.Keys
6 | import com.umeng.analytics.MobclickAgent
7 | import com.umeng.commonsdk.UMConfigure
8 |
9 | /**
10 | * 友盟的工具类
11 | */
12 | object UmengUtils {
13 |
14 | private var currentPage: String? = null
15 |
16 | fun init(context: Context){
17 | if(BuildConfig.UMENG_APP_KEY.isNotEmpty()){
18 | UMConfigure.setLogEnabled(BuildConfig.DEBUG)
19 | UMConfigure.preInit(context.applicationContext, BuildConfig.UMENG_APP_KEY, BuildConfig.UMENG_APP_CHANNEL)
20 | if(!SettingUtils.getBoolean(Keys.IS_FIRST_USE, true)) {
21 | UMConfigure.init(context.applicationContext, BuildConfig.UMENG_APP_KEY, BuildConfig.UMENG_APP_CHANNEL, UMConfigure.DEVICE_TYPE_PHONE, "")
22 | MobclickAgent.setPageCollectionMode(MobclickAgent.PageMode.MANUAL)
23 | }
24 | }
25 | }
26 |
27 | /**
28 | * 页面路由监听
29 | */
30 | fun route(context: Context, name: String?){
31 | if(currentPage == name || name == "home") return
32 |
33 | if(currentPage != null){
34 | MobclickAgent.onPageEnd(currentPage)
35 | }
36 |
37 | if(name != null){
38 | // MobclickAgent.onEventObject(context, "Navigation", mapOf("route" to name))
39 | MobclickAgent.onPageStart(name)
40 | }
41 | currentPage = name
42 | }
43 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/account/IpassLoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.account
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import com.qust.helper.data.Keys
7 | import com.qust.helper.model.account.IPassAccount
8 | import com.qust.helper.utils.SettingUtils
9 | import com.qust.helper.viewmodel.RequestViewModel
10 | import com.qust.helper.viewmodel.extend.toastError
11 | import com.qust.helper.viewmodel.extend.toastOK
12 |
13 | class IpassLoginViewModel : RequestViewModel() {
14 |
15 | val account = mutableStateOf(SettingUtils[Keys.IPASS_ACCOUNT, ""])
16 | val password = mutableStateOf(SettingUtils[Keys.IPASS_PASSWORD, ""])
17 |
18 | var accountError by mutableStateOf("")
19 | var passwordError by mutableStateOf("")
20 |
21 | fun login(onLogin: () -> Unit){
22 | if(account.value.isEmpty()) { accountError = "请输入学号"; return }
23 | if(account.value.length < 2){ accountError = "学号格式错误"; return }
24 | if(password.value.isEmpty()) { passwordError = "请输入密码"; return }
25 |
26 | accountError = ""
27 | passwordError = ""
28 |
29 | request({
30 | val result = IPassAccount.login(account.value, password.value, true)
31 | if(result){
32 | toastOK("登录成功")
33 | onLogin()
34 | }else{
35 | toastError("用户名或密码错误")
36 | }
37 | })
38 | }
39 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/room/AppDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.room
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import com.qust.helper.room.dao.AcademicMapper
8 | import com.qust.helper.room.dao.ExamMapper
9 | import com.qust.helper.room.dao.LessonMapper
10 | import com.qust.helper.room.dao.MarkMapper
11 | import com.qust.helper.room.entity.AcademicGroupDao
12 | import com.qust.helper.room.entity.AcademicInfoDao
13 | import com.qust.helper.room.entity.ExamDao
14 | import com.qust.helper.room.entity.LessonDao
15 | import com.qust.helper.room.entity.MarkDao
16 |
17 |
18 | @Database(
19 | version = 1,
20 | exportSchema = false,
21 | entities = [
22 | LessonDao::class,
23 | MarkDao::class,
24 | ExamDao::class,
25 | AcademicInfoDao::class,
26 | AcademicGroupDao::class,
27 | ]
28 | )
29 | abstract class AppDataBase : RoomDatabase() {
30 |
31 | abstract fun lessonDao(): LessonMapper
32 |
33 | abstract fun markDao(): MarkMapper
34 |
35 | abstract fun examDao(): ExamMapper
36 |
37 | abstract fun academicDao(): AcademicMapper
38 |
39 | companion object {
40 | lateinit var INSTANCE: AppDataBase
41 |
42 | fun init(context: Context) {
43 | INSTANCE = Room.databaseBuilder(
44 | context.applicationContext,
45 | AppDataBase::class.java,
46 | "app_data"
47 | ).build()
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/data/i18n/Strings.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.data.i18n
2 |
3 | import androidx.compose.ui.text.intl.Locale
4 | import com.qust.helper.Res
5 | import kotlinx.coroutines.GlobalScope
6 | import kotlinx.coroutines.launch
7 | import org.jetbrains.compose.resources.ExperimentalResourceApi
8 |
9 | open class I18nStrings {
10 |
11 | open val TEXT_OK = "确定"
12 | open val TEXT_CANCEL = "取消"
13 |
14 | open val TEXT_LOGIN = "登录"
15 |
16 | open val TEXT_TERM = "学期"
17 | open val TEXT_SAVE_LESSON_TABLE = "保存课表"
18 |
19 | open val TEXT_NEW = "新"
20 |
21 |
22 | open val MSG_NEED_LOGIN: String = "需要登录"
23 |
24 | open val MSG_ERROR_ACCOUNT: String = "用户名或密码错误"
25 |
26 | open val MSG_ERROR_LOGIC: String = "逻辑错误"
27 |
28 | open val MSG_QUERY_TERM_START_TIME = "当前开学日期: %s\n查询到的开学日期: %s"
29 |
30 |
31 | open val ARRAY_WEEK_NAME = listOf("周一", "周二", "周三", "周四", "周五", "周六", "周日")
32 |
33 | open val ARRAY_TERM_NAME = listOf(
34 | "大一 上学期", "大一 下学期",
35 | "大二 上学期", "大二 下学期",
36 | "大三 上学期", "大三 下学期",
37 | "大四 上学期", "大四 下学期"
38 | )
39 |
40 | open val ARRAY_QUERY_LESSON_TYPE = listOf("个人课表", "班级课表")
41 |
42 | }
43 |
44 | @OptIn(ExperimentalResourceApi::class)
45 | class I18nStringBuilder(
46 | /** ISO 639 语言代码 */
47 | val code: String = Locale.current.language
48 | ){
49 | init {
50 | GlobalScope.launch {
51 | Res.readBytes("/i18n/strings_${code}.properties")
52 | }
53 | }
54 | }
55 |
56 | val Strings: I18nStrings = I18nStrings()
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/utils/SettingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import com.qust.helper.platform.utils.SettingUtilsImpl
4 | import com.tencent.mmkv.MMKV
5 |
6 | class MmkvSettingImpl : SettingUtilsImpl() {
7 |
8 | private val mmkv = MMKV.defaultMMKV()
9 |
10 | override fun getString(key: String, defValue: String) = mmkv.getString(key, defValue)
11 | override fun getInt(key: String, defValue: Int) = mmkv.getInt(key, defValue)
12 | override fun getBoolean(key: String, defValue: Boolean) = mmkv.getBoolean(key, defValue)
13 | override fun getFloat(key: String, defValue: Float) = mmkv.getFloat(key, defValue)
14 | override fun getLong(key: String, defValue: Long) = mmkv.getLong(key, defValue)
15 | override fun getStringSet(key: String, defValue: Set): Set? = mmkv.getStringSet(key, defValue)
16 |
17 | override fun putString(key: String, value: String){ mmkv.putString(key, value) }
18 | override fun putInt(key: String, value: Int) { mmkv.putInt(key, value) }
19 | override fun putBoolean(key: String, value: Boolean) { mmkv.putBoolean(key, value) }
20 | override fun putFloat(key: String, value: Float) { mmkv.putFloat(key, value) }
21 | override fun putLong(key: String, value: Long) { mmkv.putLong(key, value) }
22 | override fun putStringSet(key: String, value: Set) { mmkv.putStringSet(key, value) }
23 |
24 | override fun removeKey(key: String) = mmkv.removeValueForKey(key)
25 | }
26 |
27 | actual fun getSettingUtils(): SettingUtilsImpl = MmkvSettingImpl()
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/BasePage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.material3.Scaffold
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.ui.Alignment
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.vector.ImageVector
10 | import com.qust.helper.ui.widget.components.LoadingUI
11 | import com.qust.helper.ui.widget.layout.AppContentWithBack
12 | import com.qust.helper.ui.widget.toast.ToastUI
13 | import com.qust.helper.viewmodel.BaseViewModel
14 |
15 | abstract class BasePage(
16 | val title: String,
17 | val icon: ImageVector,
18 | ) {
19 |
20 | val key: String = javaClass.name
21 |
22 | @Composable
23 | abstract fun getViewModel(): T
24 |
25 | @Composable
26 | open fun ComposePage() {
27 | val pageController = rememberPageController()
28 | Scaffold { contentPadding ->
29 | AppContentWithBack(title = title, contentPadding = contentPadding, onBack = pageController::back) {
30 | BaseContent()
31 | }
32 | }
33 | }
34 |
35 | @Composable
36 | open fun BaseContent() {
37 | val viewModel = getViewModel()
38 | Box(modifier = Modifier.fillMaxSize()) {
39 | Content(viewModel)
40 | ToastUI(viewModel.toastData, Modifier.align(Alignment.Center))
41 | LoadingUI(viewModel)
42 | }
43 | }
44 |
45 | @Composable
46 | abstract fun Content(viewModel: T)
47 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/eas/ExamModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.eas
2 |
3 | import com.qust.helper.data.QustApi
4 | import com.qust.helper.entity.eas.Exam
5 | import com.qust.helper.model.account.EasAccount
6 | import com.qust.helper.model.database.ExamStorage
7 | import com.qust.helper.model.database.getExamStorage
8 | import com.qust.helper.utils.JSONArray
9 | import com.qust.helper.utils.JsonUtils
10 | import com.qust.helper.utils.JsonUtils.get
11 | import io.ktor.client.request.forms.FormDataContent
12 | import io.ktor.client.request.setBody
13 | import io.ktor.http.parameters
14 | import kotlinx.serialization.json.JsonObject
15 | import kotlinx.serialization.json.jsonObject
16 |
17 | object ExamModel: ExamStorage by getExamStorage() {
18 |
19 | /**
20 | * 查询考试
21 | * @param xnm 学年代码 20xx
22 | * @param xqm 学期代码 12 | 3
23 | */
24 | suspend fun queryExam(account: EasAccount, term: Int, xnm: String, xqm: String): List {
25 | val json = account.post(QustApi.GET_EXAM) {
26 | setBody(FormDataContent(parameters {
27 | append("xnm", xnm)
28 | append("xqm", xqm)
29 | append("queryModel.showCount", "999")
30 | }))
31 | }
32 |
33 | val item = JsonUtils.parseString(json)["items", JSONArray] ?: return emptyList()
34 |
35 | return List(item.size) {
36 | val js = item[it].jsonObject
37 | Exam(
38 | term = term,
39 | name = js["kcmc", ""],
40 | place = js["kssj", ""],
41 | time = js["cdmc", ""],
42 | )
43 | }
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/room/dao/AcademicMapper.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.room.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.Query
6 | import com.qust.helper.room.entity.AcademicGroupDao
7 | import com.qust.helper.room.entity.AcademicInfoDao
8 |
9 | @Dao
10 | interface AcademicMapper {
11 |
12 | @Query("SELECT * FROM academic_info")
13 | fun selectInfo(): List
14 |
15 | @Query("SELECT * FROM academic_group")
16 | fun selectGroups(): List
17 |
18 | @Query("SELECT * FROM academic_info WHERE `index` = :term")
19 | fun selectByTerm(term: Int): List
20 |
21 | @Query("SELECT * FROM academic_info WHERE `group` = :group")
22 | fun selectByGroup(group: Int): List
23 |
24 | @Query("""SELECT
25 | `index` AS 'group',
26 | '' AS type,
27 | COUNT(1) AS totalCounts,
28 | SUM(credit) AS requireCredits,
29 | SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) AS passedCounts,
30 | SUM(CASE WHEN status = 4 THEN credit ELSE 0 END) AS obtainedCredits,
31 | SUM(CASE WHEN status != 4 THEN credit ELSE 0 END) AS creditNotEarned
32 | FROM academic_info GROUP BY `index`
33 | """)
34 | fun selectGroupByTerm(): List
35 |
36 |
37 | @Insert
38 | fun insertInfo(lessons: List)
39 |
40 | @Insert
41 | fun insertGroups(lessons: List)
42 |
43 | @Query("DELETE FROM academic_info")
44 | fun clearInfo()
45 |
46 | @Query("DELETE FROM academic_group")
47 | fun clearGroups()
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/ui/Prev.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.remember
6 | import androidx.compose.ui.tooling.preview.Preview
7 | import com.qust.helper.data.i18n.Strings
8 | import com.qust.helper.entity.lesson.Lesson
9 | import com.qust.helper.ui.widget.lesson.lessonEdit.LessonWeekPicker
10 | import com.qust.helper.ui.widget.lesson.lessonTable.LessonTableUI
11 | import com.qust.helper.ui.widget.lesson.lessonTable.LessonTableUIState
12 |
13 | //@Preview(backgroundColor = 0xFFE6E6E6, showSystemUi = false)
14 | @Composable
15 | fun TestLessonTable() {
16 | val uiState = remember {
17 | val ui = LessonTableUIState()
18 | ui.setLessonTable(
19 | listOf(
20 | Lesson(
21 | colorLabel = 1,
22 | week = 1,
23 | startMinute = 480,
24 | endMinute = 600,
25 | name = "高数1",
26 | place = "明德",
27 | teacher = "老师"
28 | ),
29 |
30 | Lesson(
31 | colorLabel = 1,
32 | week = 2,
33 | startMinute = 500,
34 | endMinute = 640,
35 | name = "高数2",
36 | place = "明德",
37 | teacher = "老师"
38 | ),
39 | )
40 | )
41 |
42 | ui
43 | }
44 | LessonTableUI(uiState)
45 | }
46 |
47 |
48 | @Preview(showBackground = true, showSystemUi = true)
49 | @Composable
50 | fun TestLessonTime(){
51 | val s = remember { mutableStateOf(0) }
52 | val e = remember { mutableStateOf("") }
53 |
54 | LessonWeekPicker("上课时间", s, Strings.ARRAY_WEEK_NAME)
55 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/account/EasLoginViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.account
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import com.qust.helper.data.Keys
7 | import com.qust.helper.model.account.EasAccount
8 | import com.qust.helper.utils.DateUtils
9 | import com.qust.helper.utils.SettingUtils
10 | import com.qust.helper.viewmodel.RequestViewModel
11 | import com.qust.helper.viewmodel.extend.toastError
12 | import com.qust.helper.viewmodel.extend.toastOK
13 |
14 | class EasLoginViewModel : RequestViewModel() {
15 |
16 | val account = mutableStateOf(SettingUtils[Keys.EAS_ACCOUNT, ""])
17 | val password = mutableStateOf(SettingUtils[Keys.EAS_PASSWORD, ""])
18 |
19 | var accountError by mutableStateOf("")
20 | var passwordError by mutableStateOf("")
21 |
22 | fun login(onLogin: () -> Unit){
23 | if(account.value.isEmpty()) { accountError = "请输入学号"; return }
24 | if(account.value.length < 2){ accountError = "学号格式错误"; return }
25 | if(password.value.isEmpty()) { passwordError = "请输入密码"; return }
26 |
27 | accountError = ""
28 | passwordError = ""
29 |
30 | request({
31 | val result = EasAccount.login(account.value, password.value, true)
32 | if(result){
33 | toastOK("登录成功")
34 | val year = DateUtils.today().year.toString()
35 | val currentYear = (year.substring(0, year.length - 2) + account.value.substring(0, 2)).toInt()
36 | EasAccount.entranceDate = currentYear
37 | onLogin()
38 | }else{
39 | toastError("用户名或密码错误")
40 | }
41 |
42 | })
43 | }
44 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/eas/NoticeModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.eas
2 |
3 | import com.qust.helper.data.QustApi
4 | import com.qust.helper.entity.eas.Notice
5 | import com.qust.helper.model.account.EasAccount
6 | import com.qust.helper.utils.JSONArray
7 | import com.qust.helper.utils.JsonUtils
8 | import com.qust.helper.utils.JsonUtils.get
9 | import com.qust.helper.utils.Logger
10 | import io.ktor.client.request.forms.FormDataContent
11 | import io.ktor.client.request.setBody
12 | import io.ktor.http.parameters
13 | import kotlinx.serialization.json.JsonObject
14 | import kotlinx.serialization.json.jsonObject
15 |
16 | object NoticeModel {
17 |
18 | /**
19 | * 查询教务通知
20 | * @param page 第几页
21 | * @param pageSize 每页数量
22 | */
23 | suspend fun queryNotice(account: EasAccount, page: Int = 1, pageSize: Int = 1): List {
24 | try{
25 | val body = account.post(QustApi.EA_SYSTEM_NOTICE){
26 | setBody(FormDataContent(parameters {
27 | append("queryModel.showCount", pageSize.toString())
28 | append("queryModel.currentPage", page.toString())
29 | append("queryModel.sortName", "cjsj")
30 | append("queryModel.sortOrder", "desc")
31 | }))
32 | }
33 | if(body.startsWith("{") || body.startsWith("[")) {
34 | val item = JsonUtils.parseString(body)["items", JSONArray] ?: return emptyList()
35 | return List(item.size){ Notice.createFromJson(item[it].jsonObject) }
36 | }else{
37 | return emptyList()
38 | }
39 | }catch(e: Exception){
40 | Logger.e("'url:'${QustApi.EA_SYSTEM_NOTICE}", e)
41 | return emptyList()
42 | }
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/utils/JsonUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import kotlinx.serialization.json.Json
4 | import kotlinx.serialization.json.JsonArray
5 | import kotlinx.serialization.json.JsonObject
6 | import kotlinx.serialization.json.JsonPrimitive
7 | import kotlinx.serialization.json.boolean
8 | import kotlinx.serialization.json.double
9 | import kotlinx.serialization.json.float
10 | import kotlinx.serialization.json.int
11 | import kotlinx.serialization.json.long
12 |
13 | object JSONObject
14 | object JSONArray
15 | object JSONParam
16 |
17 | object JsonUtils {
18 |
19 | val json = Json { ignoreUnknownKeys = true }
20 |
21 | fun parseString(string: String) = json.parseToJsonElement(string) as T
22 |
23 | operator fun JsonObject.get(key: String, def: Int) = (this[key] as? JsonPrimitive)?.int ?: def
24 | operator fun JsonObject.get(key: String, def: Long) = (this[key] as? JsonPrimitive)?.long ?: def
25 | operator fun JsonObject.get(key: String, def: Float) = (this[key] as? JsonPrimitive)?.float ?: def
26 | operator fun JsonObject.get(key: String, def: String) = (this[key] as? JsonPrimitive)?.content ?: def
27 | operator fun JsonObject.get(key: String, def: Double) = (this[key] as? JsonPrimitive)?.double ?: def
28 | operator fun JsonObject.get(key: String, def: Boolean) = (this[key] as? JsonPrimitive)?.boolean ?: def
29 |
30 | operator fun JsonObject.get(key: String, type: JSONObject) = (this[key] as? JsonObject)
31 | operator fun JsonObject.get(key: String, type: JSONArray) = (this[key] as? JsonArray)
32 | operator fun JsonObject.get(key: String, type: JSONParam) = (this[key] as? JsonPrimitive)
33 | }
34 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/network/Network.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.network
2 |
3 | import com.qust.helper.utils.SettingUtils
4 | import io.ktor.client.HttpClient
5 | import io.ktor.client.HttpClientConfig
6 | import io.ktor.client.plugins.cookies.CookiesStorage
7 | import io.ktor.http.Cookie
8 | import io.ktor.http.Url
9 | import kotlinx.coroutines.sync.Mutex
10 | import kotlinx.coroutines.sync.withLock
11 |
12 | expect fun httpClient(config: HttpClientConfig<*>.() -> Unit): HttpClient
13 |
14 | /**
15 | * Cookie 持久化类
16 | */
17 | class AppCookiesStorage(
18 | private val cookieName: String
19 | ): CookiesStorage {
20 |
21 | private val mutex = Mutex()
22 | private val cookies = mutableListOf()
23 |
24 | init {
25 | SettingUtils.getStringSet(cookieName).forEach { cookieData ->
26 | val sp = cookieData.indexOfFirst { it == '=' }
27 | if(sp != -1 && sp < cookieData.length - 1){
28 | cookies.add(Cookie(cookieData.substring(0, sp), cookieData.substring(sp + 1)))
29 | }
30 | }
31 | }
32 |
33 | override suspend fun get(requestUrl: Url): List = mutex.withLock {
34 | return cookies
35 | }
36 |
37 | override suspend fun addCookie(requestUrl: Url, cookie: Cookie) {
38 | if (cookie.name.isBlank() || cookie.maxAge == 0) return
39 |
40 | mutex.withLock {
41 | cookies.removeAll { it.name == cookie.name }
42 | cookies.add(Cookie(cookie.name, cookie.value))
43 | SettingUtils.putStringSet(cookieName, cookies.map { "${it.name}=${it.value}" }.toSet())
44 | }
45 | }
46 |
47 | suspend fun clear() = mutex.withLock {
48 | cookies.clear()
49 | }
50 |
51 | override fun close() { }
52 | }
53 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/tips_warning.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
27 |
32 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/tips_warning.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
22 |
27 |
32 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/lessonTable/LessonTableModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.lessonTable
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableIntStateOf
5 | import androidx.compose.runtime.mutableStateOf
6 | import com.qust.helper.entity.lesson.TimeTable
7 | import com.qust.helper.model.SettingModel
8 | import com.qust.helper.model.database.LessonTableStorage
9 | import com.qust.helper.model.database.getLessonTableStorage
10 | import com.qust.helper.model.eas.LessonTableQueryResult
11 | import com.qust.helper.utils.LessonUtils
12 |
13 | object LessonTableModel: LessonTableStorage by getLessonTableStorage() {
14 |
15 | /** 时间表 */
16 | val _timeTable = mutableStateOf(TimeTable.DEFAULT)
17 | val timeTable by _timeTable
18 |
19 | /** 开学时间 */
20 | val _startDay = mutableStateOf(SettingModel.startDay)
21 | val startDay by _startDay
22 |
23 | /** 总周数 */
24 | val _totalWeek = mutableIntStateOf(SettingModel.totalWeek)
25 | val totalWeek by _totalWeek
26 |
27 | /** 当前周 (从 0 开始) */
28 | var _currentWeek = mutableIntStateOf(0)
29 | val currentWeek by _currentWeek
30 |
31 | /** 当前星期 ( 0 - 6, 周一 —— 周日) */
32 | var _dayOfWeek = mutableIntStateOf(0)
33 | val dayOfWeek by _dayOfWeek
34 |
35 |
36 | suspend fun saveLessonTable(result: LessonTableQueryResult){
37 | val lessons = result.lessons ?: return
38 |
39 | val lessonChange = LessonUtils.mergeLesson(lessons, getAllLesson())
40 |
41 | mergeLesson(lessonChange.first, lessonChange.second, lessonChange.third)
42 |
43 | _startDay.value = result.startDay
44 | _totalWeek.value = result.totalWeek
45 |
46 | SettingModel.startDay = result.startDay
47 | SettingModel.totalWeek = result.totalWeek
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/tips_finish.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
30 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/data/Pages.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.data
2 |
3 | import com.qust.helper.ui.page.BasePage
4 | import com.qust.helper.ui.page.EmptyPage
5 | import com.qust.helper.ui.page.HomePage
6 | import com.qust.helper.ui.page.MyPage
7 | import com.qust.helper.ui.page.account.AccountManagerPage
8 | import com.qust.helper.ui.page.account.EasLoginPage
9 | import com.qust.helper.ui.page.account.IpassLoginPage
10 | import com.qust.helper.ui.page.app.SettingPage
11 | import com.qust.helper.ui.page.eas.QueryAcademicPage
12 | import com.qust.helper.ui.page.eas.QueryExamPage
13 | import com.qust.helper.ui.page.eas.QueryLessonPage
14 | import com.qust.helper.ui.page.eas.QueryMarkPage
15 | import com.qust.helper.ui.page.eas.QueryNoticePage
16 | import com.qust.helper.ui.page.lesson.LessonTablePage
17 | import com.qust.helper.ui.page.third.DrinkPage
18 |
19 | object Pages {
20 |
21 | val Pages = mapOf(
22 | EmptyPage.key to EmptyPage,
23 |
24 | HomePage.key to HomePage,
25 |
26 | LessonTablePage.key to LessonTablePage,
27 |
28 | AccountManagerPage.key to AccountManagerPage,
29 | EasLoginPage.key to EasLoginPage,
30 | IpassLoginPage.key to IpassLoginPage,
31 |
32 | QueryLessonPage.key to QueryLessonPage,
33 | QueryMarkPage.key to QueryMarkPage,
34 | QueryExamPage.key to QueryExamPage,
35 | QueryAcademicPage.key to QueryAcademicPage,
36 | QueryNoticePage.key to QueryNoticePage,
37 |
38 | DrinkPage.key to DrinkPage,
39 |
40 | SettingPage.key to SettingPage
41 | )
42 |
43 | val defaultPages: List> = listOf(LessonTablePage, MyPage)
44 |
45 | operator fun get(key: String?) = Pages[key]
46 | }
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/tips_finish.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
30 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/IconLogin.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.IconLogin: ImageVector
10 | get() {
11 | if (_IconLogin != null) {
12 | return _IconLogin!!
13 | }
14 | _IconLogin = ImageVector.Builder(
15 | name = "Login",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(12f, 12f)
23 | curveToRelative(2.21f, 0f, 4f, -1.79f, 4f, -4f)
24 | reflectiveCurveToRelative(-1.79f, -4f, -4f, -4f)
25 | reflectiveCurveToRelative(-4f, 1.79f, -4f, 4f)
26 | reflectiveCurveToRelative(1.79f, 4f, 4f, 4f)
27 | close()
28 | moveTo(12f, 14f)
29 | curveToRelative(-2.67f, 0f, -8f, 1.34f, -8f, 4f)
30 | verticalLineToRelative(1f)
31 | curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
32 | horizontalLineToRelative(14f)
33 | curveToRelative(0.55f, 0f, 1f, -0.45f, 1f, -1f)
34 | verticalLineToRelative(-1f)
35 | curveToRelative(0f, -2.66f, -5.33f, -4f, -8f, -4f)
36 | close()
37 | }
38 | }.build()
39 |
40 | return _IconLogin!!
41 | }
42 |
43 | private var _IconLogin: ImageVector? = null
44 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/room/entity/LessonDao.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.room.entity
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import com.qust.helper.entity.lesson.Lesson
7 |
8 | @Entity(tableName = "lesson")
9 | class LessonDao(
10 |
11 | @PrimaryKey(autoGenerate = true)
12 | val id: Long = 0L,
13 |
14 | @ColumnInfo val type: Int = 0,
15 | @ColumnInfo val reference: Long = 0L,
16 |
17 | @ColumnInfo val lessonId: String = "",
18 |
19 | @ColumnInfo val colorLabel: Int = 0,
20 |
21 | @ColumnInfo val weeks: Long = 0L,
22 | @ColumnInfo val week: Int = 0,
23 | @ColumnInfo val startMinute: Int = 0,
24 | @ColumnInfo val endMinute: Int = 0,
25 |
26 | @ColumnInfo val name: String = "",
27 | @ColumnInfo val place: String = "",
28 | @ColumnInfo val teacher: String = "",
29 |
30 | @ColumnInfo val remark: String = ""
31 | )
32 |
33 | fun LessonDao.toLesson(): Lesson = Lesson(
34 | id = this.id,
35 | type = this.type,
36 | reference = this.reference,
37 | lessonId = this.lessonId,
38 | colorLabel = this.colorLabel,
39 | weeks = this.weeks,
40 | week = this.week,
41 | startMinute = this.startMinute,
42 | endMinute = this.endMinute,
43 | name = this.name,
44 | place = this.place,
45 | teacher = this.teacher,
46 | remark = this.remark,
47 | )
48 |
49 | fun Lesson.toLessonDao(): LessonDao = LessonDao(
50 | id = this.id,
51 | type = this.type,
52 | reference = this.reference,
53 | lessonId = this.lessonId,
54 | colorLabel = this.colorLabel,
55 | weeks = this.weeks,
56 | week = this.week,
57 | startMinute = this.startMinute,
58 | endMinute = this.endMinute,
59 | name = this.name,
60 | place = this.place,
61 | teacher = this.teacher,
62 | remark = this.remark,
63 | )
64 |
65 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Login.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Login: ImageVector
10 | get() {
11 | if (_Login != null) {
12 | return _Login!!
13 | }
14 | _Login = ImageVector.Builder(
15 | name = "Login",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(12f, 12f)
23 | curveToRelative(2.21f, 0f, 4f, -1.79f, 4f, -4f)
24 | reflectiveCurveToRelative(-1.79f, -4f, -4f, -4f)
25 | reflectiveCurveToRelative(-4f, 1.79f, -4f, 4f)
26 | reflectiveCurveToRelative(1.79f, 4f, 4f, 4f)
27 | close()
28 | moveTo(12f, 14f)
29 | curveToRelative(-2.67f, 0f, -8f, 1.34f, -8f, 4f)
30 | verticalLineToRelative(1f)
31 | curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
32 | horizontalLineToRelative(14f)
33 | curveToRelative(0.55f, 0f, 1f, -0.45f, 1f, -1f)
34 | verticalLineToRelative(-1f)
35 | curveToRelative(0f, -2.66f, -5.33f, -4f, -8f, -4f)
36 | close()
37 | }
38 | }.build()
39 |
40 | return _Login!!
41 | }
42 |
43 | @Suppress("ObjectPropertyName")
44 | private var _Login: ImageVector? = null
45 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | CADisableMinimumFrameDurationOnPhone
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 |
30 | UILaunchScreen
31 |
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Water.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Water: ImageVector
10 | get() {
11 | if (_Water != null) {
12 | return _Water!!
13 | }
14 | _Water = ImageVector.Builder(
15 | name = "Water",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(12f, 2f)
23 | curveToRelative(-5.33f, 4.55f, -8f, 8.48f, -8f, 11.8f)
24 | curveToRelative(0f, 4.98f, 3.8f, 8.2f, 8f, 8.2f)
25 | reflectiveCurveToRelative(8f, -3.22f, 8f, -8.2f)
26 | curveTo(20f, 10.48f, 17.33f, 6.55f, 12f, 2f)
27 | close()
28 | moveTo(7.83f, 14f)
29 | curveToRelative(0.37f, 0f, 0.67f, 0.26f, 0.74f, 0.62f)
30 | curveToRelative(0.41f, 2.22f, 2.28f, 2.98f, 3.64f, 2.87f)
31 | curveToRelative(0.43f, -0.02f, 0.79f, 0.32f, 0.79f, 0.75f)
32 | curveToRelative(0f, 0.4f, -0.32f, 0.73f, -0.72f, 0.75f)
33 | curveToRelative(-2.13f, 0.13f, -4.62f, -1.09f, -5.19f, -4.12f)
34 | curveTo(7.01f, 14.42f, 7.37f, 14f, 7.83f, 14f)
35 | close()
36 | }
37 | }.build()
38 |
39 | return _Water!!
40 | }
41 |
42 | @Suppress("ObjectPropertyName")
43 | private var _Water: ImageVector? = null
44 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/third/DrinkViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.third
2 |
3 |
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import com.qust.helper.data.Keys
8 | import com.qust.helper.model.account.DrinkAccount
9 | import com.qust.helper.model.network.NeedLoginException
10 | import com.qust.helper.utils.SettingUtils
11 | import com.qust.helper.viewmodel.RequestViewModel
12 | import com.qust.helper.viewmodel.extend.toastError
13 | import com.qust.helper.viewmodel.extend.toastWarning
14 |
15 | class DrinkViewModel : RequestViewModel(), DrinkUIEvent {
16 |
17 | private val drinkAccount = DrinkAccount
18 |
19 | val uiState = DrinkUIState(drinkAccount = drinkAccount)
20 |
21 | override fun login() {
22 | request({
23 | try {
24 | val result = drinkAccount.login(uiState.account.value, uiState.password.value, true)
25 | if(result) drinkAccount.getDrinkCode()
26 | else toastError("用户名或密码错误")
27 | } catch(e: Exception) {
28 | toastError("网络错误: ${e.message}")
29 | }
30 | })
31 | }
32 |
33 | override fun getDrinkCode() {
34 | request({
35 | try {
36 | drinkAccount.getDrinkCode()
37 | } catch(e: NeedLoginException) {
38 | toastWarning("请先登录")
39 | uiState.needLogin = true
40 | } catch(e: Exception) {
41 | toastError("刷新失败: ${e.message}")
42 | }
43 | })
44 | }
45 | }
46 |
47 | class DrinkUIState(drinkAccount: DrinkAccount) {
48 | var drinkCode by drinkAccount._drinkCode
49 | var account = mutableStateOf(SettingUtils.getString(key = Keys.DRINK_ACCOUNT))
50 | var password = mutableStateOf(SettingUtils.getString(key = Keys.DRINK_PASSWORD))
51 | var needLogin by mutableStateOf(false)
52 | }
53 |
54 | interface DrinkUIEvent {
55 | fun login() {}
56 | fun getDrinkCode() {}
57 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/data/Keys.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.data
2 |
3 | object Keys {
4 |
5 | /**
6 | * 是否是第一次使用
7 | */
8 | const val IS_FIRST_USE = "isFirstUse"
9 |
10 | /**
11 | * 教务节点
12 | */
13 | const val EA_HOST = "eaHost"
14 |
15 | /**
16 | * 使用VPN访问教务
17 | */
18 | const val EA_USE_VPN = "eaUseVpn"
19 |
20 | /**
21 | * 入学时间
22 | */
23 | const val ENTRANCE_TIME = "key_entrance_time"
24 |
25 | /**
26 | * 显示所有课程
27 | */
28 | const val KEY_SHOW_ALL_LESSON = "key_show_all_lesson"
29 |
30 | /**
31 | * 隐藏已结课课程
32 | */
33 | const val KEY_HIDE_FINISH_LESSON = "key_hide_finish_lesson"
34 |
35 | /**
36 | * 隐藏教师
37 | */
38 | const val KEY_HIDE_TEACHER = "key_hide_teacher"
39 |
40 | /**
41 | * 课表时间表 0:冬季, 1:夏季
42 | */
43 | const val KEY_TIME_TABLE = "key_time_table"
44 |
45 | /**
46 | * 高密时间表
47 | */
48 | const val KEY_GAOMI_TIME_TABLE = "key_gaomi_time_table"
49 |
50 | /**
51 | * 锁定课表
52 | */
53 | const val KEY_LOCK_LESSON = "key_lock_lesson"
54 |
55 |
56 | /**
57 | * 主题跟随系统
58 | */
59 | const val KEY_THEME_FOLLOW_SYSTEM = "key_theme_follow_system"
60 | /**
61 | * 暗色模式
62 | */
63 | const val KEY_THEME_DARK = "key_theme_dark"
64 |
65 |
66 | /**
67 | * 自动检查更新
68 | */
69 | const val KEY_AUTO_UPDATE = "key_auto_update"
70 |
71 | /**
72 | * 上次检查更新的时间
73 | */
74 | const val LAST_UPDATE_TIME = "last_update_time"
75 |
76 | /**
77 | * 上一个教务公告的ID
78 | */
79 | const val LAST_NOTICE_ID = "last_notice_id"
80 |
81 |
82 | /** 课表设置 */
83 | const val SETTING_TOTAL_WEEK = "setting_total_week"
84 | const val SETTING_START_DAY = "setting_start_day"
85 |
86 |
87 |
88 | const val EAS_ACCOUNT = "eas_account"
89 | const val EAS_PASSWORD = "eas_password"
90 |
91 | const val IPASS_ACCOUNT = "ipass_account"
92 | const val IPASS_PASSWORD = "ipass_password"
93 |
94 | const val DRINK_ACCOUNT = "drink_account"
95 | const val DRINK_PASSWORD = "drink_password"
96 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Visibility.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Visibility: ImageVector
10 | get() {
11 | if (_Visibility != null) {
12 | return _Visibility!!
13 | }
14 | _Visibility = ImageVector.Builder(
15 | name = "Visibility",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(12f, 4f)
23 | curveTo(7f, 4f, 2.73f, 7.11f, 1f, 11.5f)
24 | curveTo(2.73f, 15.89f, 7f, 19f, 12f, 19f)
25 | reflectiveCurveToRelative(9.27f, -3.11f, 11f, -7.5f)
26 | curveTo(21.27f, 7.11f, 17f, 4f, 12f, 4f)
27 | close()
28 | moveTo(12f, 16.5f)
29 | curveToRelative(-2.76f, 0f, -5f, -2.24f, -5f, -5f)
30 | reflectiveCurveToRelative(2.24f, -5f, 5f, -5f)
31 | reflectiveCurveToRelative(5f, 2.24f, 5f, 5f)
32 | reflectiveCurveToRelative(-2.24f, 5f, -5f, 5f)
33 | close()
34 | moveTo(12f, 8.5f)
35 | curveToRelative(-1.66f, 0f, -3f, 1.34f, -3f, 3f)
36 | reflectiveCurveToRelative(1.34f, 3f, 3f, 3f)
37 | reflectiveCurveToRelative(3f, -1.34f, 3f, -3f)
38 | reflectiveCurveToRelative(-1.34f, -3f, -3f, -3f)
39 | close()
40 | }
41 | }.build()
42 |
43 | return _Visibility!!
44 | }
45 |
46 | @Suppress("ObjectPropertyName")
47 | private var _Visibility: ImageVector? = null
48 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/components/Loading.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.components
2 |
3 | import androidx.compose.foundation.Canvas
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.height
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.geometry.CornerRadius
10 | import androidx.compose.ui.geometry.Offset
11 | import androidx.compose.ui.geometry.Size
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.unit.Dp
14 | import androidx.compose.ui.unit.dp
15 | import com.qust.helper.ui.theme.colorSuccess
16 | import com.qust.helper.ui.widget.IndeterminateProgressDialog
17 | import com.qust.helper.viewmodel.extend.LoadingAble
18 |
19 | @Composable
20 | fun LoadingUI(loading: LoadingAble){
21 | if(loading.loadingText.isNotEmpty()) IndeterminateProgressDialog(loading.loadingText)
22 | }
23 |
24 |
25 | @Composable
26 | fun TripleProgressBar(
27 | redValue: Float = 0F,
28 | greenValue: Float = 0F,
29 | height: Dp = 5.dp,
30 | redColor: Color = MaterialTheme.colorScheme.error,
31 | greenColor: Color = colorSuccess,
32 | ) {
33 | Canvas(modifier = Modifier.fillMaxWidth().height(height)) {
34 | drawRoundRect(
35 | color = Color.Gray,
36 | topLeft = Offset(0F, 0F),
37 | size = Size(size.width, size.height),
38 | cornerRadius = CornerRadius(size.height / 2F),
39 | )
40 | if(redValue > 0F) {
41 | drawRoundRect(
42 | color = redColor,
43 | topLeft = Offset(0F, 0F),
44 | size = Size(size.width * redValue, size.height),
45 | cornerRadius = CornerRadius(size.height / 2F),
46 | )
47 | }
48 | if(greenValue > 0F) {
49 | drawRoundRect(
50 | color = greenColor,
51 | topLeft = Offset(0F, 0F),
52 | size = Size(size.width * greenValue, size.height),
53 | cornerRadius = CornerRadius(size.height / 2F),
54 | )
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/drawable/tips_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
23 |
36 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/composeResources/drawable/tips_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
23 |
36 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/lesson/LessonTablePage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page.lesson
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 | import androidx.lifecycle.viewmodel.compose.viewModel
13 | import com.qust.helper.ui.drawables.Drawables
14 | import com.qust.helper.ui.drawables.GridView
15 | import com.qust.helper.ui.page.BackHandler
16 | import com.qust.helper.ui.page.BasePage
17 | import com.qust.helper.ui.widget.dialog.BottomDialog
18 | import com.qust.helper.ui.widget.lesson.lessonEdit.LessonEditUI
19 | import com.qust.helper.ui.widget.lesson.lessonTable.LessonTableUI
20 | import com.qust.helper.viewmodel.lesson.LessonTableViewModel
21 |
22 | object LessonTablePage: BasePage("学期课表", Drawables.GridView) {
23 |
24 | @Composable
25 | override fun getViewModel() = viewModel()
26 |
27 | @Composable
28 | override fun Content(viewModel: LessonTableViewModel) {
29 | BackHandler(viewModel.isEditLesson) {
30 | viewModel.isEditLesson = false
31 | }
32 |
33 | Box(Modifier.fillMaxSize()) {
34 |
35 | LessonTableUI(viewModel.tableUIState){ i, it ->
36 | viewModel.clickLesson(i, it)
37 | }
38 |
39 | Button(modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp), onClick = {
40 | viewModel.clickLesson(-1, null)
41 | }){
42 | Text("添加课程")
43 | }
44 | }
45 |
46 | LessonEditDialog(viewModel)
47 | }
48 |
49 |
50 | @Composable
51 | fun LessonEditDialog(viewModel: LessonTableViewModel) {
52 | BottomDialog(isExpanded = viewModel.isEditLesson) {
53 | LessonEditUI(
54 | uiState = viewModel.editUIState,
55 | uiEvent = viewModel,
56 | onDismiss = { viewModel.cancel() },
57 | )
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/eas/EasCompose.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page.eas
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.ColumnScope
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.RowScope
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.width
13 | import androidx.compose.foundation.layout.wrapContentSize
14 | import androidx.compose.material3.Button
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.ui.Alignment
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.unit.dp
20 | import com.qust.helper.data.i18n.Strings
21 | import com.qust.helper.ui.widget.components.ListItemPicker
22 |
23 |
24 | @Composable
25 | fun BaseEasQueryUI(
26 | pickYear: Int = 0,
27 | onYearPick: (Int) -> Unit = { },
28 | doQuery: () -> Unit = { },
29 | searchBar: @Composable RowScope.() -> Unit = { },
30 | content: @Composable ColumnScope.() -> Unit
31 | ){
32 | Column(modifier = Modifier.fillMaxSize()) {
33 | Row(
34 | modifier = Modifier.fillMaxWidth(),
35 | horizontalArrangement = Arrangement.Center,
36 | verticalAlignment = Alignment.CenterVertically
37 | ) {
38 |
39 | Text(text = Strings.TEXT_TERM)
40 |
41 | Spacer(Modifier.width(4.dp))
42 |
43 | ListItemPicker(
44 | value = Strings.ARRAY_TERM_NAME[pickYear],
45 | list = Strings.ARRAY_TERM_NAME,
46 | onValueChange = { i, _ -> onYearPick(i) },
47 | horizontalPadding = 8.dp
48 | )
49 |
50 | Spacer(Modifier.width(4.dp))
51 |
52 | searchBar()
53 |
54 | Button(modifier = Modifier.wrapContentSize().padding(8.dp), onClick = { doQuery() }) {
55 | Text(text = Strings.TEXT_OK, maxLines = 1)
56 | }
57 | }
58 |
59 | content()
60 | }
61 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/utils/LessonUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import com.qust.helper.entity.lesson.Lesson
4 |
5 | object LessonUtils {
6 |
7 | /**
8 | * 将布尔格式的课程上课时间转换为Long形式
9 | */
10 | fun getWeeksFromBooleans(booleans: Collection): Long {
11 | var weeks = 0L
12 | var tmp = 1L
13 | for(i in booleans){
14 | if(i) weeks = weeks or tmp
15 | tmp = tmp shl 1
16 | }
17 | return weeks
18 | }
19 |
20 |
21 | /**
22 | * 合并课程表
23 | * 返还两个列表,分别是 新增的课程,更新的课程,删除课程
24 | * 分开是为了给数据库用,让数据库可以正常更新
25 | */
26 | fun mergeLesson(old: List, new: List): Triple, List, List> {
27 | val newLessons = mutableListOf()
28 | val updateLessons = mutableListOf()
29 | val deleteLessons = mutableListOf()
30 |
31 | val idMap = mutableMapOf>()
32 | for(lesson in old){
33 | // 按照 lessonId 将已有课程分组,没有 lessonId 的一般为用户自定义课程
34 | if(lesson.lessonId.isNotEmpty()){
35 | idMap.getOrPut(lesson.lessonId){ mutableListOf() }.add(lesson)
36 | }
37 | }
38 |
39 | for(lesson in new) {
40 | if(lesson.lessonId.isEmpty()) {
41 | // 没有 lessonId 的直接新增
42 | newLessons.add(lesson)
43 | continue
44 | }
45 |
46 | val list = idMap[lesson.lessonId]
47 | if(list == null){
48 | // 这个 lessonId 没出现过,是新课程
49 | newLessons.add(lesson)
50 | continue
51 | }
52 |
53 | var index = 0
54 | var find = false
55 | // 从旧课表中 lessonId 相同的课程里找上课时间相同的
56 | while(index < list.size){
57 | if(list[index].startMinute == lesson.startMinute && list[index].endMinute == lesson.endMinute){
58 | find = true
59 | updateLessons.add(lesson.copy(id = list[index].id)) // 这里要复制原课程的Id让数据库知道是哪个更新了
60 | list.removeAt(index) // 从列表里删除这个课程
61 | break
62 | }
63 | index++
64 | }
65 | // 没找到上课时间相符的,说明是新课程
66 | if(!find) newLessons.add(lesson)
67 |
68 | // 旧课表中没有被匹配的课程添加到删除列表
69 | for(lessons in idMap.values){
70 | deleteLessons.addAll(lessons)
71 | }
72 | }
73 | return Triple(newLessons, updateLessons, deleteLessons)
74 | }
75 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/account/EasLoginPage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page.account
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.rememberCoroutineScope
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController
12 | import androidx.compose.ui.unit.dp
13 | import androidx.lifecycle.viewmodel.compose.viewModel
14 | import com.qust.helper.data.i18n.Strings
15 | import com.qust.helper.ui.drawables.Drawables
16 | import com.qust.helper.ui.drawables.IconLogin
17 | import com.qust.helper.ui.page.BasePage
18 | import com.qust.helper.ui.page.rememberPageController
19 | import com.qust.helper.ui.widget.form.AccountInput
20 | import com.qust.helper.viewmodel.account.EasLoginViewModel
21 | import kotlinx.coroutines.launch
22 |
23 | object EasLoginPage: BasePage("教务登陆", Drawables.IconLogin) {
24 |
25 | @Composable
26 | override fun getViewModel() = viewModel()
27 |
28 | @Composable
29 | override fun Content(viewModel: EasLoginViewModel) {
30 | val scope = rememberCoroutineScope()
31 | val pageController = rememberPageController()
32 | val keyboardController = LocalSoftwareKeyboardController.current
33 |
34 | Column(Modifier.padding(horizontal = 16.dp)) {
35 | AccountInput(
36 | account = viewModel.account,
37 | password = viewModel.password,
38 | accountError = viewModel.accountError,
39 | passwordError = viewModel.passwordError,
40 | "学号",
41 | "教务系统密码",
42 | login = {
43 | keyboardController?.hide()
44 | viewModel.login { scope.launch { pageController.back() } }
45 | }
46 | )
47 | Button(modifier = Modifier.fillMaxWidth().padding(8.dp), onClick = {
48 | keyboardController?.hide()
49 | viewModel.login { scope.launch { pageController.back() } }
50 | }){
51 | Text(text = Strings.TEXT_OK)
52 | }
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/utils/SettingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import com.qust.helper.platform.utils.SettingUtilsImpl
4 |
5 | expect fun getSettingUtils(): SettingUtilsImpl
6 |
7 | object SettingUtils {
8 |
9 | val impl = getSettingUtils()
10 |
11 | operator fun get(key: String, defValue: T): T {
12 | return when(defValue){
13 | is String -> (impl.getString(key, defValue) ?: defValue) as T
14 | is Int -> impl.getInt(key, defValue) as T
15 | is Boolean -> impl.getBoolean(key, defValue) as T
16 | is Float -> impl.getFloat(key, defValue) as T
17 | null -> throw IllegalArgumentException("unsupported mmkv value null")
18 | else -> throw IllegalArgumentException("unsupported mmkv value type ${defValue!!::class.simpleName}")
19 | }
20 | }
21 |
22 | operator fun set(key: String, value: T){
23 | when(value){
24 | is String -> impl.putString(key, value)
25 | is Int -> impl.putInt(key, value)
26 | is Boolean -> impl.putBoolean(key, value)
27 | is Float -> impl.putFloat(key, value)
28 | null -> throw IllegalArgumentException("unsupported mmkv value null")
29 | else -> throw IllegalArgumentException("unsupported mmkv value type ${value!!::class.simpleName}")
30 | }
31 | }
32 |
33 | fun getString(key: String, defValue: String = "") = impl.getString(key, defValue) ?: defValue
34 | fun putString(key: String, value: String) = impl.putString(key, value)
35 |
36 | fun getInt(key: String, defValue: Int = 0): Int = impl.getInt(key, defValue)
37 | fun putInt(key: String, value: Int) = impl.putInt(key, value)
38 |
39 | fun getLong(key: String, defValue: Long = 0): Long = impl.getLong(key, defValue)
40 | fun putLong(key: String, value: Long) = impl.putLong(key, value)
41 |
42 | fun getBoolean(key: String, defValue: Boolean = false) = impl.getBoolean(key, defValue)
43 | fun putBoolean(key: String, value: Boolean) = impl.putBoolean(key, value)
44 |
45 | fun getStringSet(key: String, defValue: Set = emptySet()) = impl.getStringSet(key, defValue) ?: emptySet()
46 | fun putStringSet(key: String, value: Set) = impl.putStringSet(key, value)
47 |
48 | fun removeKey(key: String) = impl.removeKey(key)
49 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/model/database/AcademicStorage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.database
2 |
3 | import com.qust.helper.entity.eas.AcademicGroup
4 | import com.qust.helper.entity.eas.AcademicInfo
5 | import com.qust.helper.room.AppDataBase
6 | import com.qust.helper.room.entity.AcademicGroupDao
7 | import com.qust.helper.room.entity.AcademicInfoDao
8 | import com.qust.helper.room.entity.toAcademicGroup
9 | import com.qust.helper.room.entity.toAcademicGroupDao
10 | import com.qust.helper.room.entity.toAcademicInfo
11 | import com.qust.helper.room.entity.toAcademicInfoDao
12 |
13 | actual fun getAcademicStorage(): AcademicStorage = AcademicStorageImpl()
14 |
15 | class AcademicStorageImpl: AcademicStorage {
16 |
17 | override suspend fun getInfo(): List {
18 | return AppDataBase.INSTANCE.academicDao().selectInfo().map(AcademicInfoDao::toAcademicInfo)
19 | }
20 |
21 | override suspend fun getInfoByTerm(term: Int): List {
22 | return AppDataBase.INSTANCE.academicDao().selectByTerm(term).map(AcademicInfoDao::toAcademicInfo)
23 | }
24 |
25 | override suspend fun getInfoByGroup(group: Int): List {
26 | return AppDataBase.INSTANCE.academicDao().selectByGroup(group).map(AcademicInfoDao::toAcademicInfo)
27 | }
28 |
29 | override suspend fun getGroups(): List {
30 | return AppDataBase.INSTANCE.academicDao().selectGroups().map(AcademicGroupDao::toAcademicGroup)
31 | }
32 |
33 | override suspend fun getGroupsByTerm(): List {
34 | return AppDataBase.INSTANCE.academicDao().selectGroupByTerm().map(AcademicGroupDao::toAcademicGroup)
35 | }
36 |
37 | override suspend fun insertInfo(info: List) {
38 | AppDataBase.INSTANCE.academicDao().insertInfo(info.map(AcademicInfo::toAcademicInfoDao))
39 | }
40 |
41 | override suspend fun insertGroups(groups: List) {
42 | AppDataBase.INSTANCE.academicDao().insertGroups(groups.map(AcademicGroup::toAcademicGroupDao))
43 | }
44 |
45 | override suspend fun clearInfo() {
46 | AppDataBase.INSTANCE.academicDao().clearInfo()
47 | }
48 |
49 | override suspend fun clearGroups() {
50 | AppDataBase.INSTANCE.academicDao().clearGroups()
51 | }
52 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/kotlin/com/qust/helper/utils/NotificationUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import android.app.Notification
4 | import android.app.NotificationChannel
5 | import android.app.NotificationManager
6 | import android.content.Context
7 | import android.graphics.BitmapFactory
8 | import android.graphics.Color
9 | import android.os.Build
10 | import androidx.core.app.NotificationCompat
11 | import com.qust.helper.R
12 |
13 | object NotificationUtils {
14 |
15 | private const val NOTIFICATION_ID = "push"
16 | private const val NOTIFICATION_NAME = "推送通知"
17 | private const val NOTIFICATION_SHOW_AT_MOST = 10
18 |
19 | private var notificationNum = 0
20 |
21 | fun sendNotification(context: Context, title: String?, content: String) {
22 | if(notificationNum++ > NOTIFICATION_SHOW_AT_MOST) notificationNum = 0
23 |
24 | val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
25 |
26 | // 检查渠道
27 | if(Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
28 | if(manager.getNotificationChannel(NOTIFICATION_ID) == null) {
29 | val channel = NotificationChannel(
30 | NOTIFICATION_ID,
31 | NOTIFICATION_NAME,
32 | NotificationManager.IMPORTANCE_HIGH
33 | )
34 | manager.createNotificationChannel(channel)
35 | }
36 | }
37 |
38 | val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NOTIFICATION_ID)
39 | .setAutoCancel(true)
40 | .setSmallIcon(R.mipmap.ic_launcher)
41 | .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
42 | .setWhen(System.currentTimeMillis())
43 | .setDefaults(NotificationCompat.DEFAULT_ALL)
44 | .setPriority(Notification.PRIORITY_MAX)
45 | .setContentTitle(title)
46 | .setContentText(content)
47 | .setColor(Color.TRANSPARENT)
48 | .setCategory(NotificationCompat.CATEGORY_MESSAGE)
49 | .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
50 | // .addAction(R.mipmap.ic_avatar, "去看看", pendingIntent)
51 |
52 | if(content.length > 20) {
53 | builder.setStyle(NotificationCompat.BigTextStyle().bigText(content))
54 | }
55 |
56 | manager.notify(notificationNum, builder.build())
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/account/IpassLoginPage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page.account
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxWidth
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.material3.Button
7 | import androidx.compose.material3.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.rememberCoroutineScope
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController
12 | import androidx.compose.ui.unit.dp
13 | import androidx.lifecycle.viewmodel.compose.viewModel
14 | import com.qust.helper.data.i18n.Strings
15 | import com.qust.helper.ui.drawables.Drawables
16 | import com.qust.helper.ui.drawables.IconLogin
17 | import com.qust.helper.ui.page.BasePage
18 | import com.qust.helper.ui.page.rememberPageController
19 | import com.qust.helper.ui.widget.form.AccountInput
20 | import com.qust.helper.viewmodel.account.IpassLoginViewModel
21 | import kotlinx.coroutines.launch
22 |
23 | object IpassLoginPage: BasePage("智慧青科大登陆", Drawables.IconLogin) {
24 |
25 | @Composable
26 | override fun getViewModel() = viewModel()
27 |
28 | @Composable
29 | override fun Content(viewModel: IpassLoginViewModel) {
30 | val scope = rememberCoroutineScope()
31 | val pageController = rememberPageController()
32 | val keyboardController = LocalSoftwareKeyboardController.current
33 |
34 | Column(Modifier.padding(horizontal = 16.dp)) {
35 | AccountInput(
36 | account = viewModel.account,
37 | password = viewModel.password,
38 | accountError = viewModel.accountError,
39 | passwordError = viewModel.passwordError,
40 | "学号",
41 | "智慧青科大密码",
42 | login = {
43 | keyboardController?.hide()
44 | viewModel.login { scope.launch { pageController.back() } }
45 | }
46 | )
47 | Button(modifier = Modifier.fillMaxWidth().padding(8.dp), onClick = {
48 | keyboardController?.hide()
49 | viewModel.login { scope.launch { pageController.back() } }
50 | }){
51 | Text(text = Strings.TEXT_OK)
52 | }
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/eas/QueryExamViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.eas
2 |
3 | import androidx.compose.runtime.toMutableStateList
4 | import com.qust.helper.data.i18n.Strings
5 | import com.qust.helper.entity.eas.Exam
6 | import com.qust.helper.model.account.EasAccount
7 | import com.qust.helper.model.eas.ExamModel
8 | import com.qust.helper.model.eas.MarkModel
9 | import com.qust.helper.viewmodel.extend.toastError
10 | import com.qust.helper.viewmodel.extend.toastOK
11 |
12 | class QueryExamViewModel: BaseEasViewModel() {
13 |
14 | val exams = emptyList().toMutableStateList()
15 |
16 | private val examData: MutableList?> = MutableList(Strings.ARRAY_TERM_NAME.size) { null }
17 |
18 | fun query(){
19 | request({
20 | val term = pickYear.intValue
21 | val picker = getPickTerm()
22 | val resultData = ExamModel.queryExam(EasAccount, term, picker.first, picker.second)
23 |
24 | val origData = examData[term] ?: ExamModel.getExamsByTerm(term)
25 |
26 | if(origData.isEmpty()) {
27 | ExamModel.insertAll(resultData)
28 | examData[term] = null
29 | changeTerm(term)
30 | toastOK("查询完成")
31 | return@request
32 | }
33 |
34 | val origin = origData.toSet()
35 | val result = resultData.toSet()
36 |
37 | val new = result subtract origin
38 | val update = origin intersect result
39 | if(new.isNotEmpty() || update.isNotEmpty()) {
40 | ExamModel.updateAll(update.toList())
41 | ExamModel.insertAll(new.toList())
42 | examData[term] = null
43 | changeTerm(term)
44 | toastOK("新查询到 ${new.size} 门考试")
45 | }else{
46 | toastOK("未查询到新考试")
47 | }
48 | }) {
49 | it.printStackTrace()
50 | toastError("查询失败:${it.message}")
51 | }
52 | }
53 |
54 | fun changeTerm(index: Int){
55 | val data = examData[index]
56 | if(data == null){
57 | request({
58 | val data = ExamModel.getExamsByTerm(index)
59 | examData[index] = data
60 | exams.clear()
61 | exams.addAll(data)
62 | })
63 | }else{
64 | exams.clear()
65 | exams.addAll(data)
66 | }
67 | }
68 |
69 | fun clearNew(index: Int) {
70 | runBackGround {
71 | MarkModel.setRead(exams[index].id)
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/composeApp/src/androidMain/res/layout/widget_lesson.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
24 |
25 |
31 |
32 |
41 |
42 |
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/composeApp/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
44 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/eas/MarkModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.eas
2 |
3 | import com.qust.helper.data.QustApi
4 | import com.qust.helper.entity.eas.Mark
5 | import com.qust.helper.model.account.EasAccount
6 | import com.qust.helper.model.database.MarkStorage
7 | import com.qust.helper.model.database.getMarkStorage
8 | import com.qust.helper.utils.JSONArray
9 | import com.qust.helper.utils.JsonUtils
10 | import com.qust.helper.utils.JsonUtils.get
11 | import io.ktor.client.request.forms.FormDataContent
12 | import io.ktor.client.request.setBody
13 | import io.ktor.http.parameters
14 | import kotlinx.serialization.json.JsonObject
15 | import kotlinx.serialization.json.jsonObject
16 |
17 |
18 | object MarkModel: MarkStorage by getMarkStorage() {
19 |
20 | /**
21 | * 查询课表信息
22 | * @param year 学年
23 | * @param term 学期
24 | */
25 | suspend fun queryMarks(easAccount: EasAccount, term: Int, xnm: String, xqm: String): List {
26 | val markMap = HashMap(32)
27 |
28 | try {
29 | val json = easAccount.post(QustApi.GET_MARK){
30 | setBody(FormDataContent(parameters {
31 | append("xnm", xnm)
32 | append("xqm", xqm)
33 | append("queryModel.showCount", "999")
34 | }))
35 | }
36 |
37 | JsonUtils.parseString(json)["items", JSONArray]?.forEach { item ->
38 | val js = item.jsonObject
39 | val name = js["kcmc", ""]
40 | if(!markMap.containsKey(name)) {
41 | markMap[name] = Mark.Builder(js)
42 | }
43 | }
44 | } catch(_: Exception) {
45 | return emptyList()
46 | }
47 |
48 | try {
49 | val json = easAccount.post(QustApi.GET_MARK_DETAIL){
50 | setBody(FormDataContent(parameters {
51 | append("xnm", xnm)
52 | append("xqm", xqm)
53 | append("queryModel.showCount", "999")
54 | }))
55 | }
56 |
57 | JsonUtils.parseString(json)["items", JSONArray]?.forEach { item ->
58 | val js = item.jsonObject
59 | val name = js["kcmc", ""]
60 | var mark = markMap[name]
61 | if(mark == null) {
62 | mark = Mark.Builder(js)
63 | markMap[mark.name] = mark
64 | }
65 | mark.addItemMark(js)
66 | }
67 | } catch(e: Exception) {
68 | e.printStackTrace()
69 | }
70 | return markMap.values.map { it.build(term, true) }
71 | }
72 |
73 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/eas/QueryLessonViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.eas
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableIntStateOf
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import com.qust.helper.data.i18n.Strings
8 | import com.qust.helper.model.account.EasAccount
9 | import com.qust.helper.model.eas.LessonQuery
10 | import com.qust.helper.model.eas.LessonTableQueryResult
11 | import com.qust.helper.model.lessonTable.LessonTableModel
12 | import com.qust.helper.ui.widget.lesson.lessonTable.LessonTableUIState
13 | import com.qust.helper.utils.DateUtils
14 | import com.qust.helper.viewmodel.extend.toastError
15 | import com.qust.helper.viewmodel.extend.toastOK
16 | import com.qust.helper.viewmodel.extend.toastWarning
17 |
18 | class QueryLessonViewModel: BaseEasViewModel() {
19 |
20 | val lessonUIState = LessonTableUIState()
21 |
22 | var termText by mutableStateOf("")
23 | var termTimeText by mutableStateOf("")
24 | var totalWeek by mutableIntStateOf(LessonTableModel.totalWeek)
25 |
26 | var needSave by mutableStateOf(false)
27 |
28 | val pickType = mutableIntStateOf(0)
29 |
30 | var queryResult: LessonTableQueryResult? = null
31 |
32 | fun queryLesson(){
33 | request({
34 | val pair = getPickTerm()
35 |
36 | val result = LessonQuery.queryLessonTable(easAccount = EasAccount, pair.first, pair.second)
37 |
38 | val error = result.error
39 | if(error != null) {
40 | toastError(error)
41 | return@request
42 | }
43 |
44 | needSave = true
45 |
46 | termText = result.termText
47 | totalWeek = result.totalWeek
48 |
49 | termTimeText = Strings.MSG_QUERY_TERM_START_TIME.format(
50 | DateUtils.YMD.format(LessonTableModel.startDay),
51 | DateUtils.YMD.format(result.startDay)
52 | )
53 |
54 | val lessons = result.lessons
55 | if(lessons != null) lessonUIState.setLessonTable(lessons)
56 |
57 | queryResult = result
58 | needSave = true
59 |
60 | toastOK("获取课表成功!")
61 | })
62 | }
63 |
64 | fun saveLesson(){
65 | val result = queryResult
66 |
67 | if(result == null) {
68 | toastWarning("请先查询课表")
69 | return
70 | }
71 |
72 | request({
73 | LessonTableModel.saveLessonTable(result)
74 | }, {
75 | toastError("保存课表失败")
76 | })
77 | }
78 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/School.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.School: ImageVector
10 | get() {
11 | if (_School != null) {
12 | return _School!!
13 | }
14 | _School = ImageVector.Builder(
15 | name = "School",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(5f, 13.18f)
23 | verticalLineToRelative(2.81f)
24 | curveToRelative(0f, 0.73f, 0.4f, 1.41f, 1.04f, 1.76f)
25 | lineToRelative(5f, 2.73f)
26 | curveToRelative(0.6f, 0.33f, 1.32f, 0.33f, 1.92f, 0f)
27 | lineToRelative(5f, -2.73f)
28 | curveToRelative(0.64f, -0.35f, 1.04f, -1.03f, 1.04f, -1.76f)
29 | verticalLineToRelative(-2.81f)
30 | lineToRelative(-6.04f, 3.3f)
31 | curveToRelative(-0.6f, 0.33f, -1.32f, 0.33f, -1.92f, 0f)
32 | lineTo(5f, 13.18f)
33 | close()
34 | moveTo(11.04f, 3.52f)
35 | lineToRelative(-8.43f, 4.6f)
36 | curveToRelative(-0.69f, 0.38f, -0.69f, 1.38f, 0f, 1.76f)
37 | lineToRelative(8.43f, 4.6f)
38 | curveToRelative(0.6f, 0.33f, 1.32f, 0.33f, 1.92f, 0f)
39 | lineTo(21f, 10.09f)
40 | lineTo(21f, 16f)
41 | curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
42 | reflectiveCurveToRelative(1f, -0.45f, 1f, -1f)
43 | lineTo(23f, 9.59f)
44 | curveToRelative(0f, -0.37f, -0.2f, -0.7f, -0.52f, -0.88f)
45 | lineToRelative(-9.52f, -5.19f)
46 | curveToRelative(-0.6f, -0.32f, -1.32f, -0.32f, -1.92f, 0f)
47 | close()
48 | }
49 | }.build()
50 |
51 | return _School!!
52 | }
53 |
54 | @Suppress("ObjectPropertyName")
55 | private var _School: ImageVector? = null
56 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/model/account/DrinkAccount.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.model.account
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.qust.helper.data.Keys
5 | import com.qust.helper.utils.JSONObject
6 | import com.qust.helper.utils.JsonUtils
7 | import com.qust.helper.utils.JsonUtils.get
8 | import com.qust.helper.utils.SettingUtils
9 | import io.ktor.client.request.HttpRequestBuilder
10 | import io.ktor.client.request.setBody
11 | import io.ktor.client.statement.HttpResponse
12 | import io.ktor.client.statement.bodyAsText
13 | import io.ktor.http.URLProtocol
14 | import kotlinx.serialization.json.JsonObject
15 |
16 | object DrinkAccount: Account(
17 | protocol = URLProtocol.HTTPS,
18 | host = "dcxy-customer-app.dcrym.com",
19 | accountKey = Keys.DRINK_ACCOUNT,
20 | passwordKey = Keys.DRINK_PASSWORD,
21 | cookieKey = "drinkCookie"
22 | ) {
23 |
24 | private var userToken = SettingUtils.getString("drinkToken")
25 | set(value){
26 | field = value
27 | SettingUtils["drinkToken"] = value
28 | }
29 |
30 | val _drinkCode = mutableStateOf(SettingUtils.getString("drinkCode"))
31 | fun setDrinkCodeValue(value: String) {
32 | _drinkCode.value = value
33 | SettingUtils["drinkCode"] = value
34 | }
35 |
36 | override suspend fun baseLogin(account: String, password: String): Boolean {
37 | try {
38 | val resp = postOriginal("app/customer/login"){
39 | headers.append("Content-Type", "application/json;charset=UTF-8")
40 | setBody("""{"loginAccount": "$account", "password": "$password"}""")
41 | }.bodyAsText()
42 | val js = JsonUtils.parseString(resp)
43 | if(js["code", 0] == 1000) {
44 | val data = js["data", JSONObject] ?: return false
45 | userToken = data["token", ""]
46 | return userToken.isNotEmpty()
47 | } else {
48 | return false
49 | }
50 | }catch(e: Exception){
51 | e.printStackTrace()
52 | return false
53 | }
54 | }
55 |
56 | override suspend fun execute(request: HttpRequestBuilder): HttpResponse {
57 | request.headers.append("clientsource", "{}")
58 | request.headers.append("token", userToken)
59 | return super.execute(request)
60 | }
61 |
62 | suspend fun getDrinkCode(){
63 | val js = JsonUtils.parseString(get("app/customer/flush/idbar"))
64 | val data: String = js["data", ""]
65 | setDrinkCodeValue(data.substring(0, data.length - 1) + "3")
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Notification.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Notification: ImageVector
10 | get() {
11 | if (_Notification != null) {
12 | return _Notification!!
13 | }
14 | _Notification = ImageVector.Builder(
15 | name = "Notification",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(19.29f, 17.29f)
23 | lineTo(18f, 16f)
24 | verticalLineToRelative(-5f)
25 | curveToRelative(0f, -3.07f, -1.64f, -5.64f, -4.5f, -6.32f)
26 | lineTo(13.5f, 4f)
27 | curveToRelative(0f, -0.83f, -0.67f, -1.5f, -1.5f, -1.5f)
28 | reflectiveCurveToRelative(-1.5f, 0.67f, -1.5f, 1.5f)
29 | verticalLineToRelative(0.68f)
30 | curveTo(7.63f, 5.36f, 6f, 7.92f, 6f, 11f)
31 | verticalLineToRelative(5f)
32 | lineToRelative(-1.29f, 1.29f)
33 | curveToRelative(-0.63f, 0.63f, -0.19f, 1.71f, 0.7f, 1.71f)
34 | horizontalLineToRelative(13.17f)
35 | curveToRelative(0.9f, 0f, 1.34f, -1.08f, 0.71f, -1.71f)
36 | close()
37 | moveTo(16f, 17f)
38 | lineTo(8f, 17f)
39 | verticalLineToRelative(-6f)
40 | curveToRelative(0f, -2.48f, 1.51f, -4.5f, 4f, -4.5f)
41 | reflectiveCurveToRelative(4f, 2.02f, 4f, 4.5f)
42 | verticalLineToRelative(6f)
43 | close()
44 | moveTo(12f, 22f)
45 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
46 | horizontalLineToRelative(-4f)
47 | curveToRelative(0f, 1.1f, 0.89f, 2f, 2f, 2f)
48 | close()
49 | }
50 | }.build()
51 |
52 | return _Notification!!
53 | }
54 |
55 | @Suppress("ObjectPropertyName")
56 | private var _Notification: ImageVector? = null
57 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.5.2"
3 |
4 | androidx-activityCompose = "1.9.3"
5 | androidx-lifecycle = "2.8.4"
6 | compose-multiplatform = "1.7.0"
7 |
8 | androidxRoom = "2.7.0-alpha01"
9 |
10 | ksp = "2.1.10-1.0.31"
11 | kotlin = "2.1.0"
12 | kotlinx-datetime = "0.6.2"
13 | kotlinx-coroutines = "1.10.1"
14 |
15 |
16 | ktor = "3.0.3"
17 | mmkv = "1.2.15"
18 | sqlite = "2.5.0-SNAPSHOT"
19 |
20 |
21 | [libraries]
22 | androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
23 | androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
24 | androidx-lifecycle-viewmodel-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
25 | androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
26 | androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidxRoom" }
27 | androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidxRoom" }
28 |
29 | kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinx-datetime" }
30 | kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
31 |
32 | ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
33 | ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
34 | ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
35 |
36 | mmkv = { module = "com.tencent:mmkv", version.ref = "mmkv" }
37 | sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
38 |
39 |
40 |
41 | [plugins]
42 | androidApplication = { id = "com.android.application", version.ref = "agp" }
43 | androidLibrary = { id = "com.android.library", version.ref = "agp" }
44 | composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
45 | composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
46 | kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
47 | room = { id = "androidx.room", version.ref = "androidxRoom" }
48 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/dialog/BottomDialog.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.dialog
2 |
3 | import androidx.compose.animation.core.animateFloatAsState
4 | import androidx.compose.animation.core.tween
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.fillMaxSize
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.offset
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.graphics.Color
19 | import androidx.compose.ui.layout.onGloballyPositioned
20 | import androidx.compose.ui.platform.LocalDensity
21 | import androidx.compose.ui.unit.Dp
22 | import androidx.compose.ui.unit.dp
23 |
24 | @Composable
25 | fun BottomDialog(
26 | isExpanded: Boolean,
27 | content: @Composable () -> Unit
28 | ) {
29 | val density = LocalDensity.current
30 | var screenHeight by remember { mutableStateOf(0.dp) }
31 |
32 | Box(modifier = Modifier.fillMaxSize().onGloballyPositioned { coordinates ->
33 | with(density) {
34 | screenHeight = coordinates.size.height.toDp()
35 | }
36 | }) {
37 | BottomSlideComponent(
38 | isExpanded = isExpanded,
39 | screenHeight = screenHeight,
40 | content = content
41 | )
42 | }
43 | }
44 |
45 | @Composable
46 | fun BottomSlideComponent(
47 | isExpanded: Boolean,
48 | screenHeight: Dp,
49 | content: @Composable () -> Unit
50 | ) {
51 | var visibility by remember { mutableStateOf(false) }
52 |
53 | val animationProgress by animateFloatAsState(
54 | targetValue = if(isExpanded){
55 | visibility = true
56 | 1f
57 | } else 0f,
58 | animationSpec = tween(durationMillis = 500),
59 | label = "slide_animation"
60 | ){
61 | if(!isExpanded) visibility = false
62 | }
63 |
64 | if(!visibility) return
65 |
66 | val offset = screenHeight - screenHeight * animationProgress
67 |
68 | if(animationProgress < 0.99f) {
69 | Box(modifier = Modifier.fillMaxWidth().background(Color.Black.copy(alpha = 0.3f * (1 - animationProgress)),))
70 | }
71 |
72 | Box(modifier = Modifier.fillMaxWidth().height(screenHeight).offset(y = offset).background(MaterialTheme.colorScheme.surface)) {
73 | content()
74 | }
75 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Article.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Article: ImageVector
10 | get() {
11 | if (_Article != null) {
12 | return _Article!!
13 | }
14 | _Article = ImageVector.Builder(
15 | name = "Article",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f,
20 | autoMirror = true
21 | ).apply {
22 | path(fill = SolidColor(Color(0xFF000000))) {
23 | moveTo(19f, 3f)
24 | horizontalLineTo(5f)
25 | curveTo(3.9f, 3f, 3f, 3.9f, 3f, 5f)
26 | verticalLineToRelative(14f)
27 | curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f)
28 | horizontalLineToRelative(14f)
29 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
30 | verticalLineTo(5f)
31 | curveTo(21f, 3.9f, 20.1f, 3f, 19f, 3f)
32 | close()
33 | moveTo(13f, 17f)
34 | horizontalLineTo(8f)
35 | curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
36 | curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
37 | horizontalLineToRelative(5f)
38 | curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
39 | curveTo(14f, 16.55f, 13.55f, 17f, 13f, 17f)
40 | close()
41 | moveTo(16f, 13f)
42 | horizontalLineTo(8f)
43 | curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
44 | curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
45 | horizontalLineToRelative(8f)
46 | curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
47 | curveTo(17f, 12.55f, 16.55f, 13f, 16f, 13f)
48 | close()
49 | moveTo(16f, 9f)
50 | horizontalLineTo(8f)
51 | curveTo(7.45f, 9f, 7f, 8.55f, 7f, 8f)
52 | curveToRelative(0f, -0.55f, 0.45f, -1f, 1f, -1f)
53 | horizontalLineToRelative(8f)
54 | curveToRelative(0.55f, 0f, 1f, 0.45f, 1f, 1f)
55 | curveTo(17f, 8.55f, 16.55f, 9f, 16f, 9f)
56 | close()
57 | }
58 | }.build()
59 |
60 | return _Article!!
61 | }
62 |
63 | @Suppress("ObjectPropertyName")
64 | private var _Article: ImageVector? = null
65 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/entity/eas/Academic.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.entity.eas
2 |
3 | import com.qust.helper.utils.JsonUtils.get
4 | import kotlinx.serialization.json.JsonObject
5 |
6 | /**
7 | * 学业情况查询课程
8 | *
9 | * @param kchId 课程号
10 | * @param name 课程名称
11 | * @param type 课程类型
12 | *
13 | * @param status 修读状态
14 | *
15 | * @param credit 学分
16 | * @param mark 成绩
17 | * @param gpa 绩点
18 | *
19 | * @param category 课程类别名称
20 | * @param content 课程组成
21 | *
22 | * @param index 学年学期索引
23 | */
24 | data class AcademicInfo(
25 | val id: Int = 0,
26 |
27 | val kchId: String,
28 | val name: String,
29 | val type: String,
30 |
31 | val credit: Float,
32 | val mark: String,
33 | val gpa: Float,
34 |
35 | val status: Int = 0,
36 |
37 | val category: String,
38 | val content: String,
39 |
40 | val index: Int,
41 | val group: Int,
42 | ){
43 | companion object {
44 | fun createFromJson(js: JsonObject, entranceTime: Int, group: Int): AcademicInfo {
45 | val year: Int = if(js.contains("XNM")) {
46 | js["XNM", ""].toIntOrNull() ?: 0
47 | } else {
48 | js["JYXDXNM", ""].toIntOrNull() ?: 0
49 | }
50 |
51 | val term = if(js.contains("XQMMC")) {
52 | js["XQMMC", ""].toIntOrNull() ?: 0
53 | } else {
54 | js["JYXDXQMC", ""].toIntOrNull() ?: 0
55 | }
56 |
57 | return AcademicInfo(
58 | kchId = js["KCH_ID", ""],
59 | name = js["KCMC", ""].trim(),
60 | type = js["KCXZMC", ""],
61 |
62 | credit = js["XF", ""].toFloatOrNull() ?: 0F,
63 | mark = js["MAXCJ", ""],
64 | gpa = js["JD", 0F],
65 |
66 | status = js["XDZT", ""].toIntOrNull() ?: 0,
67 |
68 | category = js["KCLBMC", ""],
69 | content = js["XSXXXX", ""],
70 |
71 | index = (year - entranceTime) * 2 + term - 1,
72 | group = group
73 | )
74 | }
75 | }
76 | }
77 |
78 | /**
79 | * @param group 课程分组
80 | * @param type 课程性质名称
81 | * @param requireCredits 要求学分
82 | * @param obtainedCredits 已获得学分
83 | * @param creditNotEarned 未通过学分
84 | * @param passedCounts 已通过门数
85 | * @param totalCounts 总共门数
86 | */
87 | data class AcademicGroup(
88 | val group: Int,
89 | val type: String = "",
90 | val requireCredits: Float = 0f,
91 | val obtainedCredits: Float = 0f,
92 | val creditNotEarned: Float = 0f,
93 | val passedCounts: Int = 0,
94 | val totalCounts: Int = 0
95 | ){
96 | class Builder(
97 | var group: Int = 0,
98 | var type: String = "",
99 | var requireCredits: Float = 0f,
100 | var obtainedCredits: Float = 0f,
101 | var creditNotEarned: Float = 0f,
102 | var passedCounts: Int = 0,
103 | var totalCounts: Int = 0,
104 | ) {
105 | fun build() = AcademicGroup(group, type, requireCredits, obtainedCredits, creditNotEarned, passedCounts, totalCounts)
106 | }
107 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/viewmodel/app/SettingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.viewmodel.app
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import com.qust.helper.model.SettingModel
6 | import com.qust.helper.model.account.EasAccount
7 | import com.qust.helper.model.lessonTable.LessonTableModel
8 | import com.qust.helper.utils.DateUtils
9 | import com.qust.helper.utils.Logger
10 | import com.qust.helper.viewmodel.BaseViewModel
11 | import com.qust.helper.viewmodel.extend.toastWarning
12 |
13 | class SettingViewModel: BaseViewModel() {
14 |
15 | val totalWeek by LessonTableModel._totalWeek
16 | fun setTotalWeek(weekStr: String){
17 | val week = weekStr.toIntOrNull()
18 | if(week == null){
19 | toastWarning("请输入正确的数字")
20 | return
21 | }
22 | SettingModel.totalWeek = week
23 | LessonTableModel._totalWeek.value = week
24 | }
25 |
26 | val _startDay = mutableStateOf(DateUtils.YMD.format(LessonTableModel.startDay))
27 | val startDay by _startDay
28 | fun setStartDay(startDayStr: String){
29 | try {
30 | val date = DateUtils.YMD.parse(startDayStr)
31 |
32 | _startDay.value = startDayStr
33 |
34 | SettingModel.startDay = date
35 | LessonTableModel._startDay.value = date
36 | } catch(e: Exception) {
37 | Logger.e("", e)
38 | toastWarning("请输入正确的日期")
39 | }
40 | }
41 |
42 | val entranceTime = mutableStateOf(EasAccount.entranceDate.toString())
43 | fun setEntranceTime(value: Int) {
44 | entranceTime.value = value.toString()
45 | EasAccount.entranceDate = value
46 | }
47 |
48 | // val eaHost by mutableIntStateOf(SettingUtils[Keys.EA_HOST, 0])
49 | // fun setEaHostValue(value: Int){
50 | // EASAccount.getInstance().changeHost(value)
51 | // eaHost = value
52 | // }
53 |
54 | // var eaUseVpn by mutableStateOf(SettingUtils.getBoolean(Keys.EA_USE_VPN, false)); private set
55 | // fun setEaUseVpnValue(value: Boolean){
56 | // EASAccount.useVpn = value
57 | // SettingUtils.putBoolean(Keys.EA_USE_VPN, value)
58 | // eaUseVpn = value
59 | // }
60 |
61 | // var themeFollowSystem by SettingModel.themeFollowSystem; private set
62 | // fun setThemeFollowSystemValue(value: Boolean){
63 | // SettingUtils.putBoolean(Keys.KEY_THEME_FOLLOW_SYSTEM, value)
64 | // themeFollowSystem = value
65 | // }
66 | // var themeDark by SettingModel.themeDark; private set
67 | // fun setThemeDarkValue(value: Boolean){
68 | // SettingUtils.putBoolean(Keys.KEY_THEME_DARK, value)
69 | // themeDark = value
70 | // }
71 |
72 | // var autoUpdate by mutableStateOf(SettingUtils.getBoolean(Keys.KEY_AUTO_UPDATE, true)); private set
73 | // fun setAutoUpdateValue(value: Boolean){
74 | // SettingUtils.putBoolean(Keys.KEY_AUTO_UPDATE, value)
75 | // autoUpdate = value
76 | // }
77 |
78 | // val buildTime by mutableStateOf(BuildConfig.PACKAGE_TIME)
79 | // val appVersion by mutableStateOf(BuildConfig.VERSION_NAME)
80 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/layout/AppLayout.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.layout
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.ColumnScope
7 | import androidx.compose.foundation.layout.PaddingValues
8 | import androidx.compose.foundation.layout.Row
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.foundation.layout.heightIn
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.material.icons.Icons
14 | import androidx.compose.material.icons.automirrored.filled.ArrowBack
15 | import androidx.compose.material3.Icon
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.draw.clip
22 | import androidx.compose.ui.unit.dp
23 |
24 | @Composable
25 | fun AppContentWithBack(
26 | title: String,
27 | contentPadding: PaddingValues,
28 | onBack: (() -> Unit)? = null,
29 | content: @Composable ColumnScope.() -> Unit
30 | ) {
31 | Column(Modifier.fillMaxSize().padding(contentPadding)) {
32 | Row(modifier = Modifier.heightIn(min = 64.dp), verticalAlignment = Alignment.CenterVertically) {
33 | if(onBack != null){
34 | Box(modifier = Modifier.padding(start = 8.dp).clip(RoundedCornerShape(32.dp)).clickable(onClick = onBack)){
35 | Icon(
36 | imageVector = Icons.AutoMirrored.Filled.ArrowBack,
37 | contentDescription = "Back",
38 | modifier = Modifier.padding(8.dp)
39 | )
40 | }
41 | }
42 | Text(
43 | text = title,
44 | modifier = Modifier.padding(16.dp),
45 | style = MaterialTheme.typography.titleLarge,
46 | maxLines = 1,
47 | )
48 | }
49 |
50 | Column(Modifier.weight(1F), content = content)
51 | }
52 | }
53 |
54 | @Composable
55 | fun AppContent(
56 | title: String,
57 | contentPadding: PaddingValues,
58 | content: @Composable ColumnScope.() -> Unit
59 | ) {
60 | Column(Modifier.fillMaxSize().padding(contentPadding)) {
61 | Row(modifier = Modifier.heightIn(min = 64.dp), verticalAlignment = Alignment.CenterVertically) {
62 | Text(
63 | text = title,
64 | modifier = Modifier.padding(16.dp),
65 | style = MaterialTheme.typography.titleLarge,
66 | maxLines = 1,
67 | )
68 | }
69 | Column(Modifier.weight(1F), content = content)
70 | }
71 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/utils/DateUtils.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.utils
2 |
3 | import kotlinx.datetime.Clock
4 | import kotlinx.datetime.Instant
5 | import kotlinx.datetime.LocalDate
6 | import kotlinx.datetime.LocalDateTime
7 | import kotlinx.datetime.TimeZone
8 | import kotlinx.datetime.format.char
9 | import kotlinx.datetime.isoDayNumber
10 | import kotlinx.datetime.minus
11 | import kotlinx.datetime.toInstant
12 | import kotlinx.datetime.toLocalDateTime
13 |
14 | object DateUtils {
15 |
16 | val YMD_HMS = LocalDateTime.Format {
17 | year()
18 | char('-')
19 | monthNumber()
20 | char('-')
21 | dayOfMonth()
22 | char(' ')
23 | hour()
24 | char(':')
25 | minute()
26 | char(':')
27 | second()
28 | }
29 |
30 | val YMD_HM = LocalDateTime.Format {
31 | year()
32 | char('-')
33 | monthNumber()
34 | char('-')
35 | dayOfMonth()
36 | char(' ')
37 | hour()
38 | char(':')
39 | minute()
40 | }
41 |
42 | val YMD = LocalDate.Format {
43 | year()
44 | char('-')
45 | monthNumber()
46 | char('-')
47 | dayOfMonth()
48 | }
49 |
50 | val MD = LocalDate.Format {
51 | monthNumber()
52 | char('-')
53 | dayOfMonth()
54 | }
55 |
56 | val HM = LocalDateTime.Format {
57 | hour()
58 | char(':')
59 | minute()
60 | }
61 |
62 | fun today() = Clock.System.now().toLocalDateTime().date
63 |
64 | fun currentTime() = Clock.System.now().toLocalDateTime()
65 |
66 | /**
67 | * 计算时间差
68 | */
69 | // fun timeDifference(s: String, e: String): String {
70 | // try {
71 | // val fromDate = YMD_HM.parse(s)
72 | // val toDate = YMD_HM.parse(e)
73 | // val from = fromDate.time
74 | // val to = toDate.time
75 | // val hours = ((to - from) / (1000 * 60 * 60)).toInt()
76 | // val minutes = ((to - from) / (1000 * 60)).toInt() % 60
77 | // return if(hours != 0) {
78 | // if(minutes == 0) {
79 | // hours.toString() + "小时"
80 | // } else {
81 | // hours.toString() + "小时" + minutes + "分钟"
82 | // }
83 | // } else {
84 | // minutes.toString() + "分钟"
85 | // }
86 | // } catch(ignored: Exception) {
87 | // return ""
88 | // }
89 | // }
90 |
91 | fun calcDayOffset(startTime: LocalDate, endTime: LocalDate): Int {
92 | val offset = endTime.minus(startTime)
93 | return offset.days + offset.months * 30
94 | }
95 |
96 | fun calcWeekOffset(startTime: LocalDate, endTime: LocalDate): Int {
97 | val dayOfWeek = startTime.dayOfWeek.isoDayNumber
98 | val dayOffset = calcDayOffset(startTime, endTime)
99 | return dayOffset / 7 + if(dayOffset > 0) {
100 | if((dayOffset % 7 + dayOfWeek) > 7) 1 else 0
101 | } else {
102 | if((dayOffset % 7 + dayOfWeek) < 1) -1 else 0
103 | }
104 | }
105 | }
106 |
107 | fun Instant.toLocalDateTime() = this.toLocalDateTime(TimeZone.UTC)
108 |
109 | fun LocalDateTime.toInstant() = this.toInstant(TimeZone.UTC)
110 |
111 |
112 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/InsertInvitation.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.InsertInvitation: ImageVector
10 | get() {
11 | if (_InsertInvitation != null) {
12 | return _InsertInvitation!!
13 | }
14 | _InsertInvitation = ImageVector.Builder(
15 | name = "InsertInvitation",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(16f, 12f)
23 | horizontalLineToRelative(-3f)
24 | curveToRelative(-0.55f, 0f, -1f, 0.45f, -1f, 1f)
25 | verticalLineToRelative(3f)
26 | curveToRelative(0f, 0.55f, 0.45f, 1f, 1f, 1f)
27 | horizontalLineToRelative(3f)
28 | curveToRelative(0.55f, 0f, 1f, -0.45f, 1f, -1f)
29 | verticalLineToRelative(-3f)
30 | curveToRelative(0f, -0.55f, -0.45f, -1f, -1f, -1f)
31 | close()
32 | moveTo(16f, 2f)
33 | verticalLineToRelative(1f)
34 | lineTo(8f, 3f)
35 | lineTo(8f, 2f)
36 | curveToRelative(0f, -0.55f, -0.45f, -1f, -1f, -1f)
37 | reflectiveCurveToRelative(-1f, 0.45f, -1f, 1f)
38 | verticalLineToRelative(1f)
39 | lineTo(5f, 3f)
40 | curveToRelative(-1.11f, 0f, -1.99f, 0.9f, -1.99f, 2f)
41 | lineTo(3f, 19f)
42 | curveToRelative(0f, 1.1f, 0.89f, 2f, 2f, 2f)
43 | horizontalLineToRelative(14f)
44 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
45 | lineTo(21f, 5f)
46 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
47 | horizontalLineToRelative(-1f)
48 | lineTo(18f, 2f)
49 | curveToRelative(0f, -0.55f, -0.45f, -1f, -1f, -1f)
50 | reflectiveCurveToRelative(-1f, 0.45f, -1f, 1f)
51 | close()
52 | moveTo(18f, 19f)
53 | lineTo(6f, 19f)
54 | curveToRelative(-0.55f, 0f, -1f, -0.45f, -1f, -1f)
55 | lineTo(5f, 8f)
56 | horizontalLineToRelative(14f)
57 | verticalLineToRelative(10f)
58 | curveToRelative(0f, 0.55f, -0.45f, 1f, -1f, 1f)
59 | close()
60 | }
61 | }.build()
62 |
63 | return _InsertInvitation!!
64 | }
65 |
66 | @Suppress("ObjectPropertyName")
67 | private var _InsertInvitation: ImageVector? = null
68 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/widget/form/Login.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.widget.form
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.text.KeyboardActions
6 | import androidx.compose.foundation.text.KeyboardOptions
7 | import androidx.compose.material3.Icon
8 | import androidx.compose.material3.IconButton
9 | import androidx.compose.material3.OutlinedTextField
10 | import androidx.compose.material3.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.MutableState
13 | import androidx.compose.runtime.getValue
14 | import androidx.compose.runtime.mutableStateOf
15 | import androidx.compose.runtime.remember
16 | import androidx.compose.runtime.setValue
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.text.input.ImeAction
19 | import androidx.compose.ui.text.input.KeyboardType
20 | import androidx.compose.ui.text.input.PasswordVisualTransformation
21 | import androidx.compose.ui.text.input.VisualTransformation
22 | import androidx.compose.ui.unit.dp
23 | import com.qust.helper.ui.drawables.Drawables
24 | import com.qust.helper.ui.drawables.Visibility
25 | import com.qust.helper.ui.drawables.VisibilityOff
26 |
27 |
28 | @Composable
29 | fun AccountInput(
30 | account: MutableState,
31 | password: MutableState,
32 | accountError: String = "",
33 | passwordError: String = "",
34 | labelAccount: String = "账号",
35 | labelPassword: String = "密码",
36 | login: () -> Unit
37 | ){
38 | var passwordHidden by remember{ mutableStateOf(true) }
39 |
40 | OutlinedTextField(
41 | value = account.value,
42 | onValueChange = { account.value = it },
43 | modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
44 | singleLine = true,
45 | label = { Text(text = labelAccount) },
46 | isError = accountError.isNotEmpty(),
47 | supportingText = { if(accountError.isNotEmpty()) Text(text = accountError) },
48 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Next),
49 | maxLines = 1
50 | )
51 |
52 | OutlinedTextField(
53 | value = password.value,
54 | onValueChange = { password.value = it },
55 | modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
56 | label = { Text(text = labelPassword) },
57 | isError = passwordError.isNotEmpty(),
58 | supportingText = { if(passwordError.isNotEmpty()) Text(text = passwordError) },
59 | trailingIcon = {
60 | IconButton(onClick = { passwordHidden = !passwordHidden }){
61 | Icon(imageVector = (if(passwordHidden) Drawables.Visibility else Drawables.VisibilityOff), null)
62 | }
63 | },
64 | visualTransformation = if(passwordHidden) PasswordVisualTransformation('*') else VisualTransformation.None,
65 | keyboardOptions = KeyboardOptions(autoCorrectEnabled = false, keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
66 | keyboardActions = KeyboardActions(onDone = { login() }),
67 | maxLines = 1
68 | )
69 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/Brightness.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.Brightness: ImageVector
10 | get() {
11 | if (_Brightness != null) {
12 | return _Brightness!!
13 | }
14 | _Brightness = ImageVector.Builder(
15 | name = "Brightness",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(20f, 8.69f)
23 | lineTo(20f, 6f)
24 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
25 | horizontalLineToRelative(-2.69f)
26 | lineToRelative(-1.9f, -1.9f)
27 | curveToRelative(-0.78f, -0.78f, -2.05f, -0.78f, -2.83f, 0f)
28 | lineTo(8.69f, 4f)
29 | lineTo(6f, 4f)
30 | curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f)
31 | verticalLineToRelative(2.69f)
32 | lineToRelative(-1.9f, 1.9f)
33 | curveToRelative(-0.78f, 0.78f, -0.78f, 2.05f, 0f, 2.83f)
34 | lineToRelative(1.9f, 1.9f)
35 | lineTo(4f, 18f)
36 | curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f)
37 | horizontalLineToRelative(2.69f)
38 | lineToRelative(1.9f, 1.9f)
39 | curveToRelative(0.78f, 0.78f, 2.05f, 0.78f, 2.83f, 0f)
40 | lineToRelative(1.9f, -1.9f)
41 | lineTo(18f, 20f)
42 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
43 | verticalLineToRelative(-2.69f)
44 | lineToRelative(1.9f, -1.9f)
45 | curveToRelative(0.78f, -0.78f, 0.78f, -2.05f, 0f, -2.83f)
46 | lineTo(20f, 8.69f)
47 | close()
48 | moveTo(12f, 18f)
49 | curveToRelative(-3.31f, 0f, -6f, -2.69f, -6f, -6f)
50 | reflectiveCurveToRelative(2.69f, -6f, 6f, -6f)
51 | reflectiveCurveToRelative(6f, 2.69f, 6f, 6f)
52 | reflectiveCurveToRelative(-2.69f, 6f, -6f, 6f)
53 | close()
54 | moveTo(12f, 8f)
55 | curveToRelative(-2.21f, 0f, -4f, 1.79f, -4f, 4f)
56 | reflectiveCurveToRelative(1.79f, 4f, 4f, 4f)
57 | reflectiveCurveToRelative(4f, -1.79f, 4f, -4f)
58 | reflectiveCurveToRelative(-1.79f, -4f, -4f, -4f)
59 | close()
60 | }
61 | }.build()
62 |
63 | return _Brightness!!
64 | }
65 |
66 | @Suppress("ObjectPropertyName")
67 | private var _Brightness: ImageVector? = null
68 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/VisibilityOff.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.VisibilityOff: ImageVector
10 | get() {
11 | if (_VisibilityOff != null) {
12 | return _VisibilityOff!!
13 | }
14 | _VisibilityOff = ImageVector.Builder(
15 | name = "VisibilityOff",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color(0xFF000000))) {
22 | moveTo(12f, 6.5f)
23 | curveToRelative(2.76f, 0f, 5f, 2.24f, 5f, 5f)
24 | curveToRelative(0f, 0.51f, -0.1f, 1f, -0.24f, 1.46f)
25 | lineToRelative(3.06f, 3.06f)
26 | curveToRelative(1.39f, -1.23f, 2.49f, -2.77f, 3.18f, -4.53f)
27 | curveTo(21.27f, 7.11f, 17f, 4f, 12f, 4f)
28 | curveToRelative(-1.27f, 0f, -2.49f, 0.2f, -3.64f, 0.57f)
29 | lineToRelative(2.17f, 2.17f)
30 | curveToRelative(0.47f, -0.14f, 0.96f, -0.24f, 1.47f, -0.24f)
31 | close()
32 | moveTo(2.71f, 3.16f)
33 | curveToRelative(-0.39f, 0.39f, -0.39f, 1.02f, 0f, 1.41f)
34 | lineToRelative(1.97f, 1.97f)
35 | curveTo(3.06f, 7.83f, 1.77f, 9.53f, 1f, 11.5f)
36 | curveTo(2.73f, 15.89f, 7f, 19f, 12f, 19f)
37 | curveToRelative(1.52f, 0f, 2.97f, -0.3f, 4.31f, -0.82f)
38 | lineToRelative(2.72f, 2.72f)
39 | curveToRelative(0.39f, 0.39f, 1.02f, 0.39f, 1.41f, 0f)
40 | curveToRelative(0.39f, -0.39f, 0.39f, -1.02f, 0f, -1.41f)
41 | lineTo(4.13f, 3.16f)
42 | curveToRelative(-0.39f, -0.39f, -1.03f, -0.39f, -1.42f, 0f)
43 | close()
44 | moveTo(12f, 16.5f)
45 | curveToRelative(-2.76f, 0f, -5f, -2.24f, -5f, -5f)
46 | curveToRelative(0f, -0.77f, 0.18f, -1.5f, 0.49f, -2.14f)
47 | lineToRelative(1.57f, 1.57f)
48 | curveToRelative(-0.03f, 0.18f, -0.06f, 0.37f, -0.06f, 0.57f)
49 | curveToRelative(0f, 1.66f, 1.34f, 3f, 3f, 3f)
50 | curveToRelative(0.2f, 0f, 0.38f, -0.03f, 0.57f, -0.07f)
51 | lineTo(14.14f, 16f)
52 | curveToRelative(-0.65f, 0.32f, -1.37f, 0.5f, -2.14f, 0.5f)
53 | close()
54 | moveTo(14.97f, 11.17f)
55 | curveToRelative(-0.15f, -1.4f, -1.25f, -2.49f, -2.64f, -2.64f)
56 | lineToRelative(2.64f, 2.64f)
57 | close()
58 | }
59 | }.build()
60 |
61 | return _VisibilityOff!!
62 | }
63 |
64 | @Suppress("ObjectPropertyName")
65 | private var _VisibilityOff: ImageVector? = null
66 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/data/QustApi.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.data
2 |
3 | object QustApi {
4 | /**
5 | * 智慧青科大VPN HOST
6 | */
7 | const val VPN_HOST = "wvpn.qust.edu.cn"
8 |
9 | /**
10 | * 智慧青科大VPN登录入口
11 | *
12 | * Get:
13 | * - 登录界面
14 | *
15 | * Post:
16 | * - ul: 用户名长度
17 | * - pl: 密码长度
18 | * - lt: HTML里拿
19 | * - rsa: 加密后的用户名密码
20 | * - execution: e1s1
21 | * - _eventId: submit
22 | */
23 | const val VPN_LOGIN = "https://wvpn.qust.edu.cn/"
24 |
25 | /**
26 | * 智慧青科大 HOST
27 | */
28 | const val IPASS_HOST = "ipass.qust.edu.cn"
29 |
30 | /**
31 | * 智慧青科大登录入口
32 | *
33 | * Get:
34 | * - 登录界面
35 | *
36 | * Post:
37 | * - ul: 用户名长度
38 | * - pl: 密码长度
39 | * - lt: HTML里拿
40 | * - rsa: 加密后的用户名密码
41 | * - execution: e1s1
42 | * - _eventId: submit
43 | */
44 | const val IPASS_LOGIN = "http://ipass.qust.edu.cn/tpass/login/"
45 |
46 | /**
47 | * 教务系统HOST
48 | */
49 | val EA_HOSTS = arrayOf(
50 | "jwglxt.qust.edu.cn",
51 | "jwglxt1.qust.edu.cn",
52 | "jwglxt2.qust.edu.cn",
53 | "jwglxt3.qust.edu.cn",
54 | "jwglxt4.qust.edu.cn",
55 | "jwglxt5.qust.edu.cn",
56 | "jwglxt6.qust.edu.cn"
57 | )
58 |
59 |
60 | /**
61 | * 教务登录
62 | *
63 | * Get:
64 | * - 登录界面
65 | *
66 | * Post:
67 | * - csrftoken: HTML里拿
68 | * - language: zh_CN
69 | * - yhm: 用户名
70 | * - mm: RSA加密后的密码
71 | */
72 | const val EA_LOGIN = "jwglxt/xtgl/login_slogin.html"
73 |
74 | /**
75 | * 教务登录,获取RSA公钥
76 | */
77 | const val EA_LOGIN_PUBLIC_KEY = "jwglxt/xtgl/login_getPublicKey.html"
78 |
79 | /**
80 | *
81 | */
82 | const val EA_MAIN_MENU = "jwglxt/xtgl/index_initMenu.html"
83 |
84 | /**
85 | * 教务系统消息查询
86 | *
87 | * Post:
88 | * - queryModel.showCount: 一页显示几条
89 | * - queryModel.currentPage: 第几页
90 | * - queryModel.sortName: cjsj
91 | * - queryModel.sortOrder: desc
92 | */
93 | const val EA_SYSTEM_NOTICE = "jwglxt/xtgl/index_cxDbsy.html?doType=query"
94 |
95 | /**
96 | * 学年信息
97 | */
98 | const val EA_YEAR_DATA = "jwglxt/xtgl/index_cxAreaFive.html?localeKey=zh_CN&gnmkdm=index"
99 |
100 | /**
101 | * 查询学生课表
102 | */
103 | const val GET_LESSON_TABLE = "jwglxt/kbcx/xskbcx_cxXsgrkb.html"
104 |
105 | /**
106 | * 查询班级课表
107 | */
108 | const val GET_CLASS_LESSON_TABLE = "jwglxt/kbdy/bjkbdy_cxBjKb.html"
109 |
110 | /**
111 | * 推荐课表打印页面
112 | */
113 | const val RECOMMENDED_LESSON_TABLE_PRINTING = "jwglxt/kbdy/bjkbdy_cxBjkbdyIndex.html?gnmkdm=0&layout=default"
114 |
115 | /**
116 | * 成绩查询
117 | */
118 | const val GET_MARK = "jwglxt/cjcx/cjcx_cxXsgrcj.html?doType=query"
119 |
120 | /**
121 | * 成绩明细查询
122 | */
123 | const val GET_MARK_DETAIL = "jwglxt/cjcx/cjcx_cxXsKccjList.html"
124 |
125 | /**
126 | * 考试查询
127 | */
128 | const val GET_EXAM = "jwglxt/kwgl/kscx_cxXsksxxIndex.html?doType=query"
129 |
130 | /**
131 | * 学业情况查询界面
132 | */
133 | const val ACADEMIC_PAGE = "jwglxt/xsxy/xsxyqk_cxXsxyqkIndex.html?gnmkdm=N105515&layout=default"
134 |
135 | /**
136 | * 学业情况查询 - 课程信息
137 | */
138 | const val ACADEMIC_INFO = "jwglxt/xsxy/xsxyqk_cxJxzxjhxfyqKcxx.html"
139 |
140 | }
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/drawables/GridView.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.drawables
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.graphics.SolidColor
5 | import androidx.compose.ui.graphics.vector.ImageVector
6 | import androidx.compose.ui.graphics.vector.path
7 | import androidx.compose.ui.unit.dp
8 |
9 | val Drawables.GridView: ImageVector
10 | get() {
11 | if (_GridView != null) {
12 | return _GridView!!
13 | }
14 | _GridView = ImageVector.Builder(
15 | name = "GridView",
16 | defaultWidth = 24.dp,
17 | defaultHeight = 24.dp,
18 | viewportWidth = 24f,
19 | viewportHeight = 24f
20 | ).apply {
21 | path(fill = SolidColor(Color.Black)) {
22 | moveTo(5f, 11f)
23 | horizontalLineToRelative(4f)
24 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
25 | verticalLineTo(5f)
26 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
27 | horizontalLineTo(5f)
28 | curveTo(3.9f, 3f, 3f, 3.9f, 3f, 5f)
29 | verticalLineToRelative(4f)
30 | curveTo(3f, 10.1f, 3.9f, 11f, 5f, 11f)
31 | close()
32 | }
33 | path(fill = SolidColor(Color.Black)) {
34 | moveTo(5f, 21f)
35 | horizontalLineToRelative(4f)
36 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
37 | verticalLineToRelative(-4f)
38 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
39 | horizontalLineTo(5f)
40 | curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f)
41 | verticalLineToRelative(4f)
42 | curveTo(3f, 20.1f, 3.9f, 21f, 5f, 21f)
43 | close()
44 | }
45 | path(fill = SolidColor(Color.Black)) {
46 | moveTo(13f, 5f)
47 | verticalLineToRelative(4f)
48 | curveToRelative(0f, 1.1f, 0.9f, 2f, 2f, 2f)
49 | horizontalLineToRelative(4f)
50 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
51 | verticalLineTo(5f)
52 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
53 | horizontalLineToRelative(-4f)
54 | curveTo(13.9f, 3f, 13f, 3.9f, 13f, 5f)
55 | close()
56 | }
57 | path(fill = SolidColor(Color.Black)) {
58 | moveTo(15f, 21f)
59 | horizontalLineToRelative(4f)
60 | curveToRelative(1.1f, 0f, 2f, -0.9f, 2f, -2f)
61 | verticalLineToRelative(-4f)
62 | curveToRelative(0f, -1.1f, -0.9f, -2f, -2f, -2f)
63 | horizontalLineToRelative(-4f)
64 | curveToRelative(-1.1f, 0f, -2f, 0.9f, -2f, 2f)
65 | verticalLineToRelative(4f)
66 | curveTo(13f, 20.1f, 13.9f, 21f, 15f, 21f)
67 | close()
68 | }
69 | }.build()
70 |
71 | return _GridView!!
72 | }
73 |
74 | @Suppress("ObjectPropertyName")
75 | private var _GridView: ImageVector? = null
76 |
--------------------------------------------------------------------------------
/composeApp/src/commonMain/kotlin/com/qust/helper/ui/page/eas/QueryNoticePage.kt:
--------------------------------------------------------------------------------
1 | package com.qust.helper.ui.page.eas
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.material.ExperimentalMaterialApi
10 | import androidx.compose.material.pullrefresh.PullRefreshIndicator
11 | import androidx.compose.material.pullrefresh.pullRefresh
12 | import androidx.compose.material.pullrefresh.rememberPullRefreshState
13 | import androidx.compose.material3.Card
14 | import androidx.compose.material3.CardDefaults
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.runtime.LaunchedEffect
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.text.style.TextAlign
22 | import androidx.compose.ui.unit.dp
23 | import androidx.lifecycle.viewmodel.compose.viewModel
24 | import com.qust.helper.entity.eas.Notice
25 | import com.qust.helper.ui.drawables.Drawables
26 | import com.qust.helper.ui.drawables.Notification
27 | import com.qust.helper.ui.page.BasePage
28 | import com.qust.helper.viewmodel.eas.QueryNoticeViewModel
29 |
30 | object QueryNoticePage: BasePage("教务通知", Drawables.Notification) {
31 |
32 | @Composable
33 | override fun getViewModel() = viewModel()
34 |
35 | @Composable
36 | @OptIn(ExperimentalMaterialApi::class)
37 | override fun Content(viewModel: QueryNoticeViewModel) {
38 | val state = rememberPullRefreshState(refreshing = viewModel.refreshing, onRefresh = { viewModel.queryNotice() })
39 | LaunchedEffect(viewModel.hasRefresh){
40 | if(viewModel.notices.isEmpty() && !viewModel.hasRefresh && !viewModel.refreshing){ viewModel.queryNotice() }
41 | }
42 |
43 | Box(modifier = Modifier.fillMaxSize().pullRefresh(state)){
44 | LazyColumn(Modifier.fillMaxSize()){
45 | items(viewModel.notices.size) { index ->
46 | NoticeItem(viewModel.notices[index])
47 | }
48 | }
49 | PullRefreshIndicator(viewModel.refreshing, state, Modifier.align(Alignment.TopCenter))
50 | }
51 | }
52 |
53 | @Composable
54 | fun NoticeItem(notice: Notice){
55 | Card(
56 | modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp),
57 | colors = CardDefaults.cardColors(
58 | containerColor = MaterialTheme.colorScheme.background,
59 | ),
60 | elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
61 | ) {
62 |
63 | Column(modifier = Modifier.fillMaxWidth()) {
64 |
65 | Text(
66 | modifier = Modifier.fillMaxWidth().padding(16.dp, 8.dp),
67 | text = notice.content,
68 | style = MaterialTheme.typography.bodyMedium,
69 | )
70 |
71 | Text(
72 | text = notice.time,
73 | style = MaterialTheme.typography.bodySmall,
74 | textAlign = TextAlign.End,
75 | modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp)
76 | )
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------