Skip to content
This repository was archived by the owner on May 11, 2024. It is now read-only.

Commit dc1732c

Browse files
committed
新增 自由选择签到签退时间
新增 生成活动签到链接 修复 多次发起网络请求
1 parent 311f1cc commit dc1732c

File tree

28 files changed

+668
-302
lines changed

28 files changed

+668
-302
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ cuit第二课堂安卓客户端
33

44
## 基础功能
55
支持校外网(Webvpn)登录
6-
任意时间签到
6+
任意时间签到并可**选择**签到/签退时间
7+
生成进行中活动的签到签退链接
78

89
## 注意事项
910
登录前**请勿**通过其他方式外网登录第二课堂,否则会登录失败
10-
活动签到(即签到签退)时间选取为开始后10分钟和结束前10分钟,理论上后台只记录这两个数据
11+
活动签到(即签到签退)时间默认在进行时间内随机
1112
鉴于二课审核机制,暂不提供一键报名签到,选择活动前请甄选是否符合条件
1213

1314
## 测试环境

app/src/androidTest/java/com/thryan/secondclass/ExampleInstrumentedTest.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package com.thryan.secondclass
22

3-
import androidx.test.platform.app.InstrumentationRegistry
43
import androidx.test.ext.junit.runners.AndroidJUnit4
5-
import com.thryan.secondclass.core.Webvpn
4+
import androidx.test.platform.app.InstrumentationRegistry
65
import kotlinx.coroutines.runBlocking
7-
6+
import org.junit.Assert.*
87
import org.junit.Test
98
import org.junit.runner.RunWith
109

11-
import org.junit.Assert.*
12-
1310
/**
1411
* Instrumented test, which will execute on an Android device.
1512
*

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
android:label="@string/app_name"
1313
android:roundIcon="@mipmap/ic_launcher_round"
1414
android:supportsRtl="true"
15-
android:networkSecurityConfig="@xml/netword_config"
15+
android:networkSecurityConfig="@xml/network_config"
1616
android:theme="@style/Theme.SecondClass"
1717
tools:targetApi="31">
1818
<activity

app/src/main/java/com/thryan/secondclass/core/SecondClass.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import com.thryan.secondclass.core.utils.Requests
1212
import com.thryan.secondclass.core.utils.after
1313
import com.thryan.secondclass.core.utils.before
1414

15-
class SecondClass(val twfid: String) {
15+
class SecondClass(private val twfid: String, var token: String = "") {
1616

1717
private val requests =
1818
Requests("http://ekt-cuit-edu-cn.webvpn.cuit.edu.cn:8118/api/", Factory("JSON"))
19-
var token: String = ""
19+
2020

2121
/**
2222
* 登录第二课堂

app/src/main/java/com/thryan/secondclass/core/Webvpn.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ object Webvpn {
3939
suspend fun login(account: String, password: String): HttpResult<String> {
4040
val auth = auth()
4141
if (auth.message != "login auth success") return HttpResult(auth.message, auth.message)
42-
val vpnInfo = auth.data!!
42+
val vpnInfo = auth.data
4343
val res = requests
4444
.post<String> {
4545
path("login_psw.csp?anti_replay=1&encrypt=1&apiversion=1")

app/src/main/java/com/thryan/secondclass/core/result/ScoreInfo.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.thryan.secondclass.core.result
22

33
import kotlinx.serialization.Serializable
4-
import java.math.BigDecimal
54

65
/**
76
* @param score 积分

app/src/main/java/com/thryan/secondclass/core/result/User.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import kotlinx.serialization.Serializable
66
* @param id 用户id
77
* @param name 用户名
88
* @param sex 性别(男1女0?)
9-
* @param orgName 班级名
109
*/
1110
@Serializable
1211
data class User(val id: String, val name: String, val sex: Int)

app/src/main/java/com/thryan/secondclass/core/utils/HttpUtils.kt

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import com.thryan.secondclass.core.result.HttpResult
44
import kotlinx.coroutines.Dispatchers
55
import kotlinx.coroutines.suspendCancellableCoroutine
66
import kotlinx.coroutines.withContext
7-
import kotlinx.serialization.decodeFromString
8-
import kotlinx.serialization.json.Json
97
import okhttp3.FormBody
108
import okhttp3.MediaType.Companion.toMediaTypeOrNull
119
import okhttp3.OkHttpClient
@@ -20,8 +18,8 @@ import kotlin.coroutines.resumeWithException
2018

2119
class Requests(val url: String, val factory: Factory) {
2220

23-
suspend inline fun <reified T> get(block: Request<T>.() -> Unit): HttpResult<T> {
24-
val request = Request<T>().apply {
21+
suspend inline fun <reified T> get(block: Request.() -> Unit): HttpResult<T> {
22+
val request = Request().apply {
2523
block()
2624
}
2725
request.builder.url((url + request.api).also(::println))
@@ -32,12 +30,12 @@ class Requests(val url: String, val factory: Factory) {
3230
}
3331

3432
@JvmName("getString")
35-
suspend fun get(block: Request<String>.() -> Unit): HttpResult<String> {
33+
suspend fun get(block: Request.() -> Unit): HttpResult<String> {
3634
return this.get<String>(block)
3735
}
3836

39-
suspend inline fun <reified T> post(block: Request<T>.() -> Unit): HttpResult<T> {
40-
val request = Request<T>().apply {
37+
suspend inline fun <reified T> post(block: Request.() -> Unit): HttpResult<T> {
38+
val request = Request().apply {
4139
block()
4240
}
4341

@@ -52,11 +50,11 @@ class Requests(val url: String, val factory: Factory) {
5250
}
5351

5452
@JvmName("postString")
55-
suspend fun post(block: Request<String>.() -> Unit): HttpResult<String> {
53+
suspend fun post(block: Request.() -> Unit): HttpResult<String> {
5654
return this.post<String>(block)
5755
}
5856

59-
inner class Request<T> {
57+
inner class Request {
6058
val builder = okhttp3.Request.Builder()
6159
private var params: String = ""
6260
private var path: String = ""
@@ -174,15 +172,3 @@ class JSON {
174172
}
175173

176174

177-
class Solve<T, U> {
178-
var onSuccess: ((T) -> U)? = null
179-
var onFailure: ((String) -> String)? = null
180-
181-
fun success(block: (T) -> U) {
182-
onSuccess = block
183-
}
184-
185-
fun failure(block: (String) -> String) {
186-
onFailure = block
187-
}
188-
}

app/src/main/java/com/thryan/secondclass/core/utils/Utills.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.thryan.secondclass.core.result.HttpResult
55
import com.thryan.secondclass.core.result.SignInfo
66
import java.time.LocalDate
77
import java.time.LocalDateTime
8-
import java.time.LocalTime
98
import java.time.format.DateTimeFormatter
109

1110
fun String.after(minutes: Int): String {
@@ -22,6 +21,12 @@ fun String.before(minutes: Int): String {
2221
return oneHourBefore.format(formatter)
2322
}
2423

24+
fun String.toLocalDateTime(): LocalDateTime {
25+
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
26+
return LocalDateTime.parse(this, formatter)
27+
}
28+
29+
2530
fun String.toLocalDate(): LocalDate {
2631
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
2732
val dateTime = LocalDateTime.parse(this, formatter)

app/src/main/java/com/thryan/secondclass/ui/MainActivity.kt

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.thryan.secondclass.ui
22

33
import android.content.Context
44
import android.os.Bundle
5+
import android.util.Log
56
import androidx.activity.ComponentActivity
67
import androidx.activity.compose.setContent
78
import androidx.compose.foundation.isSystemInDarkTheme
@@ -12,7 +13,6 @@ import androidx.compose.runtime.Composable
1213
import androidx.compose.runtime.SideEffect
1314
import androidx.compose.ui.Modifier
1415
import androidx.compose.ui.graphics.Color
15-
import androidx.compose.ui.tooling.preview.Preview
1616
import androidx.core.view.WindowCompat
1717
import androidx.navigation.NavHostController
1818
import androidx.navigation.NavType
@@ -24,10 +24,11 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
2424
import com.thryan.secondclass.ui.info.Info
2525
import com.thryan.secondclass.ui.info.InfoViewModel
2626
import com.thryan.secondclass.ui.login.Login
27-
import com.thryan.secondclass.ui.theme.SecondClassTheme
2827
import com.thryan.secondclass.ui.login.LoginViewModel
29-
import com.thryan.secondclass.ui.page.PageViewModel
3028
import com.thryan.secondclass.ui.page.Page
29+
import com.thryan.secondclass.ui.page.PageIntent
30+
import com.thryan.secondclass.ui.page.PageViewModel
31+
import com.thryan.secondclass.ui.theme.SecondClassTheme
3132

3233

3334
class MainActivity : ComponentActivity() {
@@ -59,12 +60,16 @@ fun AppNavHost(
5960
startDestination: String = "login",
6061
context: Context
6162
) {
63+
var pageViewModel: PageViewModel? = null
64+
var infoViewModel: InfoViewModel? = null
6265
NavHost(
6366
modifier = modifier,
6467
navController = navController,
6568
startDestination = startDestination
6669
) {
70+
Log.i("Main", "NavBuilder")
6771
composable("login") {
72+
6873
Login(viewModel = LoginViewModel(context, navController))
6974
}
7075
composable(
@@ -74,29 +79,46 @@ fun AppNavHost(
7479
navArgument("account") { type = NavType.StringType }
7580
)
7681
) { backStackEntry ->
82+
Log.i("Main", "我他妈recompose")
7783
val twfid = backStackEntry.arguments?.getString("twfid")
7884
val account = backStackEntry.arguments?.getString("account")
79-
Page(
80-
PageViewModel(
85+
//防止viewModel多次创建多次发起网络请求
86+
if (pageViewModel == null|| checkNotNull(account) != pageViewModel!!.account) {
87+
pageViewModel = PageViewModel(
8188
navController,
8289
twfid = checkNotNull(twfid),
8390
account = checkNotNull(account)
8491
)
92+
pageViewModel!!.send(PageIntent.Init)
93+
}
94+
Page(
95+
viewModel = pageViewModel!!
8596
)
8697
}
8798
composable(
88-
"info?twfid={twfid}&id={id}",
99+
"info?id={id}&twfid={twfid}&token={token}",
89100
arguments = listOf(
90-
navArgument("id") { type = NavType.StringType }
101+
navArgument("id") { type = NavType.StringType },
102+
navArgument("twfid") { type = NavType.StringType },
103+
navArgument("token") { type = NavType.StringType }
91104
)
92105
) { backStackEntry ->
106+
Log.i("Main", "我他妈recompose")
93107
val id = backStackEntry.arguments?.getString("id")
108+
val twfid = backStackEntry.arguments?.getString("twfid")
109+
val token = backStackEntry.arguments?.getString("token")
110+
if (infoViewModel == null || checkNotNull(id) != infoViewModel!!.id) {
111+
infoViewModel = InfoViewModel(
112+
id = checkNotNull(id),
113+
twfid = checkNotNull(twfid),
114+
token = checkNotNull(token)
115+
)
116+
}
94117
Info(
95118
navController,
96-
InfoViewModel(
97-
id = checkNotNull(id)
98-
)
119+
infoViewModel!!
99120
)
121+
100122
}
101123
}
102124
}
@@ -112,12 +134,4 @@ fun TransparentSystemBars() {
112134
isNavigationBarContrastEnforced = false,
113135
)
114136
}
115-
}
116-
117-
118-
@Preview(showBackground = true)
119-
@Composable
120-
fun DefaultPreview() {
121-
SecondClassTheme {
122-
}
123137
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.thryan.secondclass.ui.component
2+
3+
import androidx.compose.foundation.layout.RowScope
4+
import androidx.compose.material3.Button
5+
import androidx.compose.material3.OutlinedButton
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.LaunchedEffect
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.runtime.mutableStateOf
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.setValue
12+
import androidx.compose.ui.Modifier
13+
import kotlinx.coroutines.delay
14+
15+
@Composable
16+
fun DebouncedButton(
17+
modifier: Modifier = Modifier,
18+
outline: Boolean = true,
19+
enabled: Boolean = true,
20+
onClick: () -> Unit,
21+
content: @Composable (RowScope.() -> Unit)
22+
) {
23+
var clicked by remember {
24+
mutableStateOf(!enabled)
25+
}
26+
LaunchedEffect(clicked) {
27+
if (clicked) {
28+
delay(1000L)
29+
clicked = !clicked
30+
}
31+
}
32+
if (!outline)
33+
Button(
34+
modifier = modifier,
35+
onClick = {
36+
if (enabled && !clicked) {
37+
clicked = true
38+
onClick()
39+
}
40+
},
41+
content = content
42+
)
43+
else
44+
OutlinedButton(
45+
modifier = modifier,
46+
onClick = {
47+
if (enabled && !clicked) {
48+
clicked = true
49+
onClick()
50+
}
51+
},
52+
content = content
53+
)
54+
55+
56+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.thryan.secondclass.ui.component
2+
3+
import android.util.Log
4+
import androidx.compose.material3.ExperimentalMaterial3Api
5+
import androidx.compose.material3.Text
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import com.marosseleng.compose.material3.datetimepickers.time.ui.dialog.TimePickerDialog
9+
import com.thryan.secondclass.ui.info.InfoIntent
10+
import com.thryan.secondclass.ui.info.InfoViewModel
11+
import java.time.LocalTime
12+
13+
14+
@OptIn(ExperimentalMaterial3Api::class)
15+
@Composable
16+
fun TimePicker(
17+
modifier: Modifier = Modifier,
18+
title: String,
19+
viewModel: InfoViewModel,
20+
onTimeChange: (LocalTime)->Unit
21+
) {
22+
TimePickerDialog(
23+
onDismissRequest = {
24+
viewModel.send(InfoIntent.CloseDialog)
25+
},
26+
onTimeChange = {
27+
onTimeChange(it)
28+
Log.i("TimePicker",it.toString())
29+
viewModel.send(InfoIntent.CloseDialog)
30+
},
31+
modifier = modifier,
32+
is24HourFormat = true,
33+
title = { Text(title) }
34+
)
35+
}

0 commit comments

Comments
 (0)