Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,31 @@ on:

jobs:
build:
name: Build Debug
name: Test, lint and build
runs-on: ubuntu-latest

steps:
- name: Checkout selected branch
if: github.event_name == 'workflow_dispatch'
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ inputs.branch }}
submodules: recursive

- name: Checkout
if: github.event_name != 'workflow_dispatch'
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
submodules: recursive

- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1
with:
distribution: temurin
java-version: "17"

- name: Set up Android SDK
uses: android-actions/setup-android@v3
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3

- name: Install SDK components
run: |
Expand All @@ -56,31 +56,40 @@ jobs:
echo "$ANDROID_HOME/ndk/28.2.13676358/toolchains/llvm/prebuilt/linux-x86_64/bin" >> $GITHUB_PATH

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
with:
toolchain: 1.94.0
targets: aarch64-linux-android
components: clippy

- name: Install cargo-ndk
run: cargo install cargo-ndk
run: cargo install cargo-ndk --version 4.1.2 --locked

- name: Set Gradle permission
run: chmod +x ./gradlew

- name: Cache Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/actions/setup-gradle@0b6dd653ba04f4f93bf581ec31e66cbd7dcb644d # v4

- name: Cache Cargo
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@49a0bdc70d2e1b713ca9e2869b211fcce03d3c1c # v2
with:
workspaces: |
sdk
GuiXu-Rust

- name: Build debug APK
run: ./gradlew :app:assembleDebug --stacktrace
- name: Test Rust SDK
run: |
cargo test --manifest-path sdk/Cargo.toml
# The pinned SDK still contains a known unsupported look-ahead regex.
# Keep every other Clippy check active until that SDK fix is merged.
cargo clippy --manifest-path sdk/Cargo.toml --all-targets -- --allow clippy::invalid_regex

- name: Test, lint and build Android
run: ./gradlew :app:testDebugUnitTest :app:lintRelease :app:assembleDebug :app:assembleRelease --stacktrace

- name: Upload debug APK
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: AHUTong-debug-apk
path: app/build/outputs/apk/debug/*.apk
Expand Down
17 changes: 5 additions & 12 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,9 @@ android {
}
}

packaging {
jniLibs {
excludes += "**/libahutong_rs.so"
}
}

lint {
//即使报错也不会停止打包
abortOnError = false
//打包release版本的时候是否进行检测
checkReleaseBuilds = false
lint {
abortOnError = true
checkReleaseBuilds = true
}
//关闭PNG合法性检查
// aaptOptions.useNewCruncher = false
Expand Down Expand Up @@ -135,7 +127,8 @@ dependencies {
implementation(libs.androidx.ui)
implementation(libs.androidx.foundation)
implementation(libs.androidx.material.icons.extended)
implementation(libs.material3)
implementation(libs.material3)
implementation(libs.miuix.android)
implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.navigation.compose)
Expand Down
51 changes: 31 additions & 20 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
-keep class com.ahu.ahutong.data.model.** { *; }
# These models are deserialized both from the native bridge and from local Gson caches. Field-only
# rules do not prevent vertical class merging, which is unsafe for reflection-based construction.
-keep class com.ahu.ahutong.data.model.** { *; }
-keep class com.ahu.ahutong.ui.screen.main.ElectricityDepositKt { *; }
-keep class com.ahu.ahutong.ui.screen.main.home.ElectricityPaymentKt { *; }
-keep class com.ahu.ahutong.data.dao.AHUCache { *; }
-keep class com.ahu.ahutong.ui.component.** { *; }
-keep class com.ahu.ahutong.ui.state.** { *; }

-keepclassmembers class kotlinx.coroutines.** {
volatile <fields>;
Expand All @@ -74,7 +74,26 @@
private *;
}

-keep class com.ahu.ahutong.data.crawler.model.** { *; }
# Crawler DTOs are Retrofit/Gson wire contracts. Keeping only their fields still allows R8 to
# merge the owning classes, which can turn a valid login response into a ClassCastException in
# minified builds. Keep the complete contracts so Review/Release authentication behaves like Debug.
-keep class com.ahu.ahutong.data.crawler.model.** { *; }

# Payment view models contain a small number of file-local wire DTOs. Keep only the DTO naming
# families rather than the ViewModels themselves, so R8 can still optimize the screen logic while
# Gson retains concrete constructors and field contracts in Review/Release builds.
-keep class com.ahu.ahutong.ui.state.*Response { *; }
-keep class com.ahu.ahutong.ui.state.*Map { *; }
-keep class com.ahu.ahutong.ui.state.*Data { *; }
-keep class com.ahu.ahutong.ui.state.*DataItem { *; }
-keep class com.ahu.ahutong.ui.state.*Details { *; }
-keep class com.ahu.ahutong.ui.state.*Payload { *; }
-keep class com.ahu.ahutong.ui.state.*FeeItem { *; }

# Native campus-card WebView bridge messages are also Gson contracts.
-keep class com.ahu.ahutong.ui.screen.main.CmbRechargeBridgePayload { *; }
-keep class com.ahu.ahutong.ui.screen.main.CmbRechargeBridgePaymentMethod { *; }
-keep class com.ahu.ahutong.ui.screen.main.CmbPaymentUiPayload { *; }


-renamesourcefileattribute AHUTong
Expand Down Expand Up @@ -160,22 +179,14 @@
-keep class com.ahu.ahutong.personalization.bootstrap.BootstrapTrainingCredentialResponse { *; }
-keep class com.ahu.ahutong.personalization.bootstrap.BootstrapTrainingDeletionRequest { *; }

# Data source interface + implementations (prevent R8 from stripping abstract methods)
-keep interface com.ahu.ahutong.data.base.BaseDataSource { *; }
-keep class com.ahu.ahutong.data.crawler.CrawlerDataSource { *; }
-keep class com.ahu.ahutong.data.crawler.SdkDataSource { *; }
-keep class com.ahu.ahutong.data.mock.MockDataSource { *; }

# Weather API + models (prevent R8 from stripping Gson/Retrofit classes)
-keep class com.ahu.ahutong.data.weather.** { *; }
-keep interface com.ahu.ahutong.data.weather.WeatherApi { *; }

# Repository / GitHub models
-keep class com.ahu.ahutong.data.repository.** { *; }

# AHURepository
-keep class com.ahu.ahutong.data.AHURepository { *; }
# Weather responses are constructed and populated reflectively by Gson. Field-only rules with
# allowoptimization let R8 remove fields that are only read through reflection, which leaves the
# release weather widget with an incomplete response model.
-keep class com.ahu.ahutong.data.weather.** { *; }
-keep interface com.ahu.ahutong.data.weather.WeatherApi { *; }

# Repository / GitHub models
-keepclassmembers,allowoptimization class com.ahu.ahutong.data.repository.** { <fields>; }

# Evaluation
-keep class com.ahu.ahutong.data.EvaluationRepository { *; }
-keep interface com.ahu.ahutong.data.crawler.api.jwxt.EvaluationApi { *; }
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
Expand All @@ -69,6 +70,10 @@ import com.ahu.ahutong.personalization.prefetch.PrefetchCoordinator
import com.ahu.ahutong.personalization.prefetch.PrefetchDiagnostic
import com.ahu.ahutong.personalization.prefetch.PrefetchState
import com.ahu.ahutong.personalization.ui.SuggestionPolicy
import com.ahu.ahutong.ui.components.LocalLiquidGlassContentBackdrop
import com.ahu.ahutong.ui.components.appLiquidGlassSceneBackground
import com.ahu.ahutong.ui.components.appLiquidGlassSurface
import com.ahu.ahutong.ui.theme.LiquidGlassSurfaceLevel
import java.time.LocalDate
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -130,6 +135,7 @@ class DebugDiagnosticsContribution @Inject constructor(
val screenWidthPx = with(density) { configuration.screenWidthDp.dp.toPx() }
val screenHeightPx = with(density) { configuration.screenHeightDp.dp.toPx() }
val ballPx = with(density) { 52.dp.toPx() }
val contentBackdrop = LocalLiquidGlassContentBackdrop.current
val horizontalInsetPx = with(density) { 12.dp.toPx() }
val minX = -(screenWidthPx - ballPx - horizontalInsetPx * 2).coerceAtLeast(0f)
val maxY = (screenHeightPx / 2f - ballPx).coerceAtLeast(0f)
Expand All @@ -143,7 +149,12 @@ class DebugDiagnosticsContribution @Inject constructor(
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.padding(end = 12.dp)
.size(52.dp)
.clip(CircleShape)
.appLiquidGlassSurface(
shape = CircleShape,
fallbackColor = MaterialTheme.colorScheme.tertiaryContainer,
level = LiquidGlassSurfaceLevel.Floating,
backdrop = contentBackdrop
)
.pointerInput(minX, maxY) {
detectDragGestures(
onDragEnd = {
Expand All @@ -161,7 +172,7 @@ class DebugDiagnosticsContribution @Inject constructor(
onLongClick = preferences::togglePaused
),
shape = CircleShape,
color = MaterialTheme.colorScheme.tertiaryContainer,
color = Color.Transparent,
shadowElevation = 8.dp
) {
Box(contentAlignment = Alignment.Center) {
Expand Down Expand Up @@ -198,7 +209,9 @@ private fun DiagnosticsScreen(
onDispose { activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) }
}
LazyColumn(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background),
modifier = Modifier
.fillMaxSize()
.appLiquidGlassSceneBackground(MaterialTheme.colorScheme.background),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Expand Down Expand Up @@ -844,11 +857,18 @@ private fun DiagnosticsSection(
title: String? = null,
content: @Composable ColumnScope.() -> Unit
) {
val shape = MaterialTheme.shapes.large
Surface(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.large,
color = MaterialTheme.colorScheme.surfaceContainer,
tonalElevation = 1.dp
modifier = Modifier
.fillMaxWidth()
.appLiquidGlassSurface(
shape = shape,
fallbackColor = MaterialTheme.colorScheme.surfaceContainer,
level = LiquidGlassSurfaceLevel.Panel
),
shape = shape,
color = Color.Transparent,
tonalElevation = 0.dp
) {
Column(
modifier = Modifier.fillMaxWidth().padding(16.dp),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
Expand All @@ -32,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
Expand All @@ -47,6 +49,8 @@ import com.ahu.ahutong.data.gray.GrayReleaseManager
import com.ahu.ahutong.data.mock.MockScenarioController
import com.ahu.ahutong.notification.CourseReminderScheduler
import com.ahu.ahutong.ui.components.LiquidToggle
import com.ahu.ahutong.ui.components.appLiquidGlassSceneBackground
import com.ahu.ahutong.ui.components.appLiquidGlassSurface
import com.ahu.ahutong.ui.shape.SmoothRoundedCornerShape
import com.ahu.ahutong.ui.state.DiscoveryViewModel
import com.ahu.ahutong.ui.state.ScheduleViewModel
Expand Down Expand Up @@ -165,6 +169,7 @@ fun Debug(
Column(
modifier = Modifier
.fillMaxSize()
.appLiquidGlassSceneBackground(96.n1 withNight 10.n1)
.verticalScroll(rememberScrollState())
.systemBarsPadding()
.padding(bottom = 80.dp)
Expand Down Expand Up @@ -490,8 +495,9 @@ fun Debug(
unfocusedTextColor = MaterialTheme.colorScheme.onSurface,
focusedLabelColor = MaterialTheme.colorScheme.primary,
unfocusedLabelColor = MaterialTheme.colorScheme.onSurfaceVariant,
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedContainerColor = subCardColor,
unfocusedContainerColor = subCardColor,
disabledContainerColor = subCardColor,
cursorColor = MaterialTheme.colorScheme.primary
)
)
Expand Down Expand Up @@ -769,11 +775,14 @@ private fun DebugSection(
cardColor: Color,
content: @Composable ColumnScope.() -> Unit
) {
val shape = SmoothRoundedCornerShape(24.dp)
Column(
modifier = Modifier
.fillMaxWidth()
.clip(SmoothRoundedCornerShape(24.dp))
.background(cardColor)
.appLiquidGlassSurface(
shape = shape,
fallbackColor = cardColor
)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
content = {
Expand Down Expand Up @@ -804,7 +813,11 @@ private fun DebugToggleRow(
.fillMaxWidth()
.clip(SmoothRoundedCornerShape(20.dp))
.background(96.n1 withNight 16.n1)
.clickable { onCheckedChange(!checked) }
.toggleable(
value = checked,
role = Role.Switch,
onValueChange = onCheckedChange
)
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
Expand All @@ -826,7 +839,8 @@ private fun DebugToggleRow(
LiquidToggle(
selected = { checked },
onSelect = onCheckedChange,
backdrop = backdrop
backdrop = backdrop,
toggleOnTap = false
)
}
}
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.POST_PROMOTED_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
Expand Down Expand Up @@ -128,7 +127,7 @@

<receiver
android:name=".appwidget.WidgetUpdateScheduler"
android:exported="true">
android:exported="false">
<intent-filter>
<action android:name="com.ahu.ahutong.appwidget.ACTION_UPDATE_WIDGETS" />
</intent-filter>
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/java/com/ahu/ahutong/AHUApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ public void onCreate() {
CourseReminderScheduler.INSTANCE.createNotificationChannel(this);
CourseReminderScheduler.INSTANCE.reschedule(this);

// 初始化数据源(根据 Mock 开关)
if(AHUCache.INSTANCE.getMockData()){
// Release builds always start on the real data source and erase legacy mock state.
if (!BuildConfig.DEBUG) {
AHUCache.INSTANCE.setMockData(false);
AHURepository.INSTANCE.initializeDataSource(false);
} else if(AHUCache.INSTANCE.getMockData()){
AHURepository.INSTANCE.initializeDataSource(true);
Toast.makeText(this,"正在使用mock数据",Toast.LENGTH_SHORT).show();
}
Expand Down
Loading
Loading