diff --git a/.gitignore b/.gitignore index b26857a..0175a5d 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ # App build outputs /app/build/ /app/release/ +# Local release staging: signed AABs and R8 mapping files for Play upload. +/release/ /glass-hud/build/ /glass-hud/local.properties diff --git a/README.md b/README.md index 5c7df6b..c194f79 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ A modern Android application for connecting to and monitoring Xiaomi/Ninebot M36 ## 📸 Screenshots +

+ Demo Ride walkthrough: dashboard, details and glasses display settings +

+ | Dashboard | Details | Glasses Display | Settings | | :-------: | :-----: | :-------------: | :------: | | Dashboard | Scooter details | Glasses display settings | Settings | diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c7d2327..f18241e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -418,8 +418,8 @@ android { // glass-hud so both APKs share one support matrix. minSdk = 28 targetSdk = 36 - versionCode = 9 - versionName = "1.5.0" + versionCode = 10 + versionName = "1.5.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 24e539e..c6fa0fd 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,68 +1,69 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle.kts. - -# ============================================ -# Missing Annotation Classes (Google Tink / Crypto) -# ============================================ --dontwarn com.google.errorprone.annotations.** --dontwarn javax.annotation.** --dontwarn javax.annotation.concurrent.** --dontwarn org.checkerframework.** --dontwarn com.google.crypto.tink.** - -# Keep Tink classes used by AndroidX Security Crypto --keep class com.google.crypto.tink.** { *; } --keepclassmembers class com.google.crypto.tink.** { *; } - -# ============================================ -# JNA (Java Native Access) - Required for Rust FFI -# ============================================ --dontwarn com.sun.jna.** --keep class com.sun.jna.** { *; } --keepclassmembers class * extends com.sun.jna.** { public *; } - -# ============================================ -# Kotlin Coroutines -# ============================================ --keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} --keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} --keepclassmembers class kotlinx.coroutines.** { - volatile ; -} --keep class kotlinx.coroutines.android.** { *; } - -# ============================================ -# Jetpack Compose -# ============================================ --keep class androidx.compose.** { *; } --dontwarn androidx.compose.** - -# ============================================ -# AndroidX Security Crypto -# ============================================ --keep class androidx.security.crypto.** { *; } - -# ============================================ -# Application Classes - Keep native interface -# ============================================ --keep class com.m365bleapp.** { *; } - -# ============================================ -# General Android -# ============================================ --keepattributes *Annotation* --keepattributes SourceFile,LineNumberTable --keep public class * extends android.app.Activity --keep public class * extends android.app.Application --keep public class * extends android.app.Service --keep public class * extends android.content.BroadcastReceiver --keep public class * extends android.content.ContentProvider - -# ============================================ -# Remove Verbose Logging in Release Build -# ============================================ --assumenosideeffects class android.util.Log { - public static int v(...); - public static int d(...); -} +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle.kts. +# +# Keep rules here must be NARROW. This file used to keep androidx.compose.**, +# com.m365bleapp.**, Tink, security-crypto and coroutines-android wholesale, +# which left most of the DEX unshrunk, unobfuscated and unoptimized: Play +# Console reported 13% on all three and flags anything below 25%. Every one +# of those libraries ships its own consumer rules; a blanket `-keep` here only +# overrides them with "touch nothing". + +# ============================================ +# Missing Annotation Classes (Google Tink / Crypto) +# ============================================ +-dontwarn com.google.errorprone.annotations.** +-dontwarn javax.annotation.** +-dontwarn javax.annotation.concurrent.** +-dontwarn org.checkerframework.** +-dontwarn com.google.crypto.tink.** + +# Tink (via AndroidX Security Crypto) parses its keysets with shaded +# protobuf-lite, which reads message fields reflectively. +-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite { + ; +} + +# ============================================ +# JNA (Java Native Access) +# ============================================ +# JNA binds Java methods to native symbols by name at runtime. +-dontwarn com.sun.jna.** +-keep class com.sun.jna.** { *; } +-keepclassmembers class * extends com.sun.jna.** { public *; } + +# ============================================ +# Rust FFI (ninebot-ffi) +# ============================================ +# The native library exports Java_com_m365bleapp_ffi_M365Native_, so +# the class name and the native method names must survive. The Rust side does +# not call back into Java (no FindClass / CallMethod), so nothing else in the +# app is reached by name from native code. +-keep class com.m365bleapp.ffi.M365Native { + native ; +} + +# ============================================ +# Kotlin Coroutines +# ============================================ +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} +-keepclassmembers class kotlinx.coroutines.** { + volatile ; +} + +# ============================================ +# General Android +# ============================================ +# Readable stack traces in Play Console (deobfuscated with mapping.txt). +-keepattributes *Annotation* +-keepattributes SourceFile,LineNumberTable +-renamesourcefileattribute SourceFile + +# ============================================ +# Remove Verbose Logging in Release Build +# ============================================ +-assumenosideeffects class android.util.Log { + public static int v(...); + public static int d(...); +} diff --git a/app/src/main/java/com/m365bleapp/MainActivity.kt b/app/src/main/java/com/m365bleapp/MainActivity.kt index 7e4d4ce..2b553e2 100644 --- a/app/src/main/java/com/m365bleapp/MainActivity.kt +++ b/app/src/main/java/com/m365bleapp/MainActivity.kt @@ -2,10 +2,12 @@ package com.m365bleapp import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.os.Bundle +import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge +import androidx.core.view.WindowCompat import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -31,7 +33,7 @@ class MainActivity : ComponentActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - enableEdgeToEdge() + drawEdgeToEdge() super.onCreate(savedInstanceState) repository = ScooterRepository.getInstance(applicationContext) @@ -52,6 +54,37 @@ class MainActivity : ComponentActivity() { } } + /** + * Edge-to-edge on every supported API level, without androidx.activity's + * `enableEdgeToEdge()`. + * + * That helper sets `LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES` on API 28-29 + * (its `EdgeToEdgeApi28`), and because minSdk is 28 that code path is in + * the release DEX. Play Console flags the constant as an API deprecated in + * Android 15, whatever device actually runs it. + * + * The rest of what the helper did is covered here or elsewhere: + * - bar colours: transparent via the window theme (themes.xml); + * - icon appearance: M365BleAppTheme sets light icons (dark-only app); + * - cutout: ALWAYS on API 30+, which is also what the helper used there. + * On API 28-29 the platform DEFAULT already lets a portrait window that + * extends under the status bar draw into the cutout. + */ + private fun drawEdgeToEdge() { + WindowCompat.setDecorFitsSystemWindows(window, false) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + window.attributes = window.attributes.apply { + layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Otherwise the system paints a translucent scrim behind the + // gesture/button navigation bar on API 29-34. + window.isStatusBarContrastEnforced = false + window.isNavigationBarContrastEnforced = false + } + } + override fun onDestroy() { super.onDestroy() // Only disconnect if the activity is actually finishing (user pressed back, etc.) diff --git a/doc/README_zh-CN.md b/doc/README_zh-CN.md index b300500..8b48a94 100644 --- a/doc/README_zh-CN.md +++ b/doc/README_zh-CN.md @@ -42,6 +42,10 @@ ## 📸 屏幕截图 +

+ 模拟骑行操作演示:仪表盘、详细信息与眼镜显示设置 +

+ | 仪表盘 | 详细信息 | 眼镜显示 | 设置 | | :----: | :------: | :------: | :--: | | 仪表盘 | 滑板车详细信息 | 眼镜显示设置 | 设置 | diff --git a/doc/README_zh-TW.md b/doc/README_zh-TW.md index 78a29fc..d4e0afd 100644 --- a/doc/README_zh-TW.md +++ b/doc/README_zh-TW.md @@ -42,6 +42,10 @@ ## 📸 螢幕截圖 +

+ 模擬騎乘操作示範:儀表板、詳細資訊與眼鏡顯示設定 +

+ | 儀表板 | 詳細資訊 | 眼鏡顯示 | 設定 | | :----: | :------: | :------: | :--: | | 儀表板 | 滑板車詳細資訊 | 眼鏡顯示設定 | 設定 | diff --git a/doc/RELEASE_NOTES_v1.5.1.md b/doc/RELEASE_NOTES_v1.5.1.md new file mode 100644 index 0000000..156900e --- /dev/null +++ b/doc/RELEASE_NOTES_v1.5.1.md @@ -0,0 +1,67 @@ +# v1.5.1 — Play Console fixes, full translations | Play Console 修正、翻譯補齊 + +Phone app `com.m365bleapp` versionCode 10, glasses app `com.m365hud.glass` +versionCode 7. Both report versionName 1.5.1. + +手機端 `com.m365bleapp` versionCode 10,眼鏡端 `com.m365hud.glass` versionCode 7, +versionName 皆為 1.5.1。 + +--- + +## Play Console issues addressed | 已處理的 Play Console 問題 + +| Issue | Fix | +| :--- | :--- | +| DEX optimization / obfuscation / shrinking at 13% (threshold 25%) | Blanket R8 keep rules removed. Phone DEX 22.5 MB → 2.5 MB, 91% of classes now renamed; glasses 95%. | +| Deprecated `LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES` (`b.r.a`) | Came from androidx.activity `enableEdgeToEdge()`; the app now sets up edge-to-edge itself and never uses that mode. | +| Edge-to-edge may not display for all users | Same change. | + +| 問題 | 修正 | +| :--- | :--- | +| DEX 最佳化/模糊化/縮減僅 13%(門檻 25%) | 移除過寬的 R8 keep 規則。手機端 DEX 22.5 MB → 2.5 MB,91% 類別已混淆;眼鏡端 95%。 | +| 已淘汰的 `LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES`(`b.r.a`) | 來源為 androidx.activity 的 `enableEdgeToEdge()`;改為自行處理無邊框,不再使用此模式。 | +| 無邊框畫面可能無法向所有使用者顯示 | 同上。 | + +## Other changes | 其他變更 + +- All 10 translations completed (58–72 missing strings each). | 補齊 10 種語言翻譯(每種缺 58–72 條)。 +- Arabic now lays out right-to-left. | 阿拉伯文改為由右至左版面。 +- Motor lock buttons no longer break in Russian / Ukrainian. | 修正俄文/烏克蘭文的馬達鎖定按鈕版面。 +- Demo Ride switch in Settings reflects its state. | 設定中的模擬騎乘開關會正確顯示狀態。 +- Refreshed store screenshots for 11 languages and a promo video. | 更新 11 種語言商店截圖與宣傳影片。 + +## Play "What's new" text | Play「最新異動」文字 + +en-US: + +``` +• Fully translated into all 11 supported languages +• Arabic now displays right-to-left +• Fixed motor lock buttons in Russian and Ukrainian +• Smaller, faster app (optimized release build) +• Better display on Android 15 edge-to-edge screens +``` + +zh-TW: + +``` +• 11 種語言翻譯全數補齊 +• 阿拉伯文改為由右至左顯示 +• 修正俄文、烏克蘭文的馬達鎖定按鈕版面 +• 安裝檔更小、執行更有效率 +• 改善 Android 15 無邊框畫面的顯示 +``` + +## Build | 建置 + +Built in WSL (Rust toolchain) with the release key +(`SHA256 7C:A3:A3:F7:…`, same as v1.5.0), so it updates the existing Play +listing. `libninebot_ffi.so` is present for all four ABIs with 16 KB +`PT_LOAD` alignment. No locally configured Rokid credential appears in either +bundle. Upload the matching `mapping-*.txt` to Play Console for readable +crash reports. + +於 WSL(含 Rust 工具鏈)以正式金鑰建置(`SHA256 7C:A3:A3:F7:…`,與 v1.5.0 相同), +可直接更新既有 Play 上架。四種 ABI 皆含 16 KB 對齊的 `libninebot_ffi.so`,兩個 +bundle 內皆無本機設定的 Rokid 憑證。請一併上傳對應的 `mapping-*.txt` 至 Play Console, +以取得可讀的當機報告。 diff --git a/doc/play-store/promo/demo.gif b/doc/play-store/promo/demo.gif new file mode 100644 index 0000000..fb7c024 Binary files /dev/null and b/doc/play-store/promo/demo.gif differ diff --git a/doc/play-store/promo/promo_1920x1080.mp4 b/doc/play-store/promo/promo_1920x1080.mp4 new file mode 100644 index 0000000..e754dfb Binary files /dev/null and b/doc/play-store/promo/promo_1920x1080.mp4 differ diff --git a/glass-hud/build.gradle.kts b/glass-hud/build.gradle.kts index abfb13d..8dd5949 100644 --- a/glass-hud/build.gradle.kts +++ b/glass-hud/build.gradle.kts @@ -31,8 +31,8 @@ android { // Aligned with the phone app so a joint release ships two APKs that // report the same versionName. versionCode must strictly increase for // sideloaded updates to install over an existing build. - versionCode = 6 - versionName = "1.5.0" + versionCode = 7 + versionName = "1.5.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/glass-hud/proguard-rules.pro b/glass-hud/proguard-rules.pro index 980438f..9ddb9f1 100644 --- a/glass-hud/proguard-rules.pro +++ b/glass-hud/proguard-rules.pro @@ -2,15 +2,10 @@ # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle.kts. -# ============================================ -# BLE related classes -# ============================================ --keep class android.bluetooth.** { *; } - -# ============================================ -# Compose -# ============================================ --keep class androidx.compose.** { *; } +# No blanket keeps for android.bluetooth.** (framework classes are never in +# the DEX, so the rule did nothing) or androidx.compose.** (Compose ships its +# own consumer rules; keeping all of it left most of the DEX unshrunk and +# unobfuscated, which Play Console flags below 25%). # ============================================ # Rokid CXR SDK -- REQUIRED, do not comment out