Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
adf038a
:bug: (ai): constrain category recognition to synced categories
Sylthionys Jun 14, 2026
4317bd9
:bug: (tap): migrate TensorFlow Lite runtime to LiteRT
Sylthionys Jun 14, 2026
96891d8
:rotating_light: (build): clean up Gradle and compiler warnings
Sylthionys Jun 14, 2026
4f7d62c
:bug: (ai): reject ambiguous child category mappings
Sylthionys Jun 14, 2026
99d7e1b
:bug: (server): avoid duplicate local server initialization
Sylthionys Jun 14, 2026
f0e2a26
:rotating_light: (build): address explicit lint findings
Sylthionys Jun 14, 2026
e3fe07e
Merge pull request #1 from Sylthionys/fix-ai-category-and-build-cleanup
Sylthionys Jun 15, 2026
b3dc7c3
:rotating_light: (app): fix lifecycle and compatibility lint warnings
Sylthionys Jun 15, 2026
81fda6c
:bug: (ocr): separate notification launch and OCR action
Sylthionys Jun 15, 2026
8d64a7e
:rotating_light: (build): enforce release lint correctness gates
Sylthionys Jun 15, 2026
def1ee9
:recycle: (ui): localize layout text and typography
Sylthionys Jun 15, 2026
a58bcf0
:wheelchair: (ui): mark decorative images inaccessible
Sylthionys Jun 15, 2026
6dc3639
:globe_with_meridians: (ui): format dynamic text with resources
Sylthionys Jun 15, 2026
62411bf
:globe_with_meridians: (ui): use plurals for counted text
Sylthionys Jun 15, 2026
015426b
:zap: (ui): use precise list and layout updates
Sylthionys Jun 15, 2026
8640ba0
:art: (resources): audit icon and vector assets
Sylthionys Jun 15, 2026
0a14e36
:recycle: (resources): replace dynamic resource lookups
Sylthionys Jun 15, 2026
de45e7e
:bookmark: (resources): retain reflected view bindings
Sylthionys Jun 15, 2026
ea606c0
:bookmark: (resources): retain generated binding layouts
Sylthionys Jun 15, 2026
46c44a5
:fire: (resources): remove verified unused resources
Sylthionys Jun 15, 2026
c2f6e61
:pushpin: (deps): pin Bugly crash reporting
Sylthionys Jun 15, 2026
961f08f
:arrow_up: (deps): align Lifecycle components
Sylthionys Jun 15, 2026
589ac7d
:arrow_up: (deps): align Kotlin coroutines
Sylthionys Jun 15, 2026
084d225
:arrow_up: (deps): update AndroidX UI foundations
Sylthionys Jun 15, 2026
74bdfa3
:arrow_up: (deps): update AndroidX Navigation
Sylthionys Jun 15, 2026
545663b
:arrow_up: (deps): update Room persistence
Sylthionys Jun 15, 2026
28ceeb5
:arrow_up: (deps): update Material components
Sylthionys Jun 15, 2026
eca30c0
:arrow_up: (test): update AndroidX test libraries
Sylthionys Jun 15, 2026
04f6a6b
:arrow_up: (deps): update MMKV storage runtime
Sylthionys Jun 15, 2026
7b45837
:arrow_up: (tap): update LiteRT runtime
Sylthionys Jun 15, 2026
11fe9a4
:arrow_up: (build): update verified AGP patch
Sylthionys Jun 15, 2026
8333a33
Merge upstream master into cleanup-lint-warnings
Sylthionys Jun 15, 2026
a7c7c82
:bug: (review): fix reload race and redact search logs
Sylthionys Jun 15, 2026
1b4ed5a
:bug: (server): preserve spaced bill state queries
Sylthionys Jun 16, 2026
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
20 changes: 18 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,25 @@ android {
)
}

bundle {
language {
enableSplit = false
}
}

lint {
checkReleaseBuilds = false
abortOnError = false
checkReleaseBuilds = true
abortOnError = true
warningsAsErrors = false
fatal += setOf(
"CheckResult",
"InlinedApi",
"NewApi",
"Recycle",
"MissingPermission",
"WrongConstant",
"LaunchActivityFromNotification",
)
}

}
Expand Down
13 changes: 10 additions & 3 deletions app/src/main/java/net/ankio/auto/service/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import net.ankio.auto.constant.WorkMode
import org.ezbook.server.intent.IntentType
import net.ankio.auto.service.api.ICoreService
import net.ankio.auto.storage.Logger
import net.ankio.auto.ui.activity.MainActivity
import net.ankio.auto.utils.PrefManager

/**
Expand Down Expand Up @@ -144,18 +145,24 @@ class CoreService : LifecycleService() {
* 创建一个低优先级、静默的通知,点击触发手动 OCR(通知不清除)
*/
private fun buildNotification(): Notification {
val appIntent = Intent(this, MainActivity::class.java)
val appPendingIntent = PendingIntent.getActivity(
this, 0, appIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val ocrIntent = Intent(this, CoreService::class.java).apply {
putExtra("intentType", IntentType.OCR.name)
putExtra("manual", true)
}
val pendingIntent = PendingIntent.getService(
this, 0, ocrIntent,
val ocrPendingIntent = PendingIntent.getService(
this, 1, ocrIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.drawable.icon_auto)
.setContentTitle(getString(R.string.service_notification_title))
.setContentIntent(pendingIntent)
.setContentIntent(appPendingIntent)
.addAction(R.drawable.ic_ocr, getString(R.string.ocr_tile_title), ocrPendingIntent)
.setOngoing(true)
.setShowWhen(false)
.setSilent(true)
Expand Down
20 changes: 16 additions & 4 deletions app/src/main/java/net/ankio/auto/service/ocr/OcrTools.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,21 @@ object OcrTools {
}

suspend fun collapseStatusBar() {
SelectToSpeakService.instance?.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
SelectToSpeakService.instance?.performGlobalAction(
AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE
)
} else {
withContext(Dispatchers.IO) {
Shell(BuildConfig.APPLICATION_ID).use { shell ->
runCatching {
if (shell.checkPermission()) {
shell.exec("cmd statusbar collapse")
}
}
}
}
}
delay(500)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.TextView
import net.ankio.auto.R
import net.ankio.auto.storage.Logger
Expand Down Expand Up @@ -79,7 +80,8 @@ class RepeatToast(

// 视图使用主题化的 Context 进行膨胀,保证主题属性可用
val themedCtx = context.toThemeCtx()
val view = LayoutInflater.from(themedCtx).inflate(R.layout.repeat_toast, null)
val inflationParent = FrameLayout(themedCtx)
val view = LayoutInflater.from(themedCtx).inflate(R.layout.repeat_toast, inflationParent, false)
rootView = view

val msgView = view.findViewById<TextView>(R.id.message)
Expand Down Expand Up @@ -155,4 +157,4 @@ class RepeatToast(
}.onFailure { Logger.w("RepeatToast removeView failed: ${it.message}") }
}
}
}
}
10 changes: 7 additions & 3 deletions app/src/main/java/net/ankio/auto/storage/backup/BackupManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ class BackupManager(private val context: Context) {
DocumentsContract.getTreeDocumentId(uri)
)

context.contentResolver.query(
val cursor = context.contentResolver.query(
childrenUri,
arrayOf(
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
Expand All @@ -308,7 +308,9 @@ class BackupManager(private val context: Context) {
null,
null,
null
)?.use { cursor ->
)
try {
if (cursor == null) return@withIO
val nameIndex =
cursor.getColumnIndexOrThrow(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
val idIndex =
Expand All @@ -322,6 +324,8 @@ class BackupManager(private val context: Context) {
backupFiles.add(Pair(name, fileUri))
}
}
} finally {
cursor?.close()
}

// 按文件名排序(文件名包含时间戳,降序排列)
Expand Down Expand Up @@ -416,4 +420,4 @@ class BackupManager(private val context: Context) {

}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ class RestoreManager(private val context: Context) {
throwable = null
)

inputStream.use { stream ->
try {
val file = File(context.cacheDir, filename)
file.writeBytes(stream.readBytes())
file.writeBytes(inputStream.readBytes())

// 解包并恢复数据
fileManager.unpackData(file)
} finally {
inputStream.close()
}

Logger.i("本地恢复完成")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ class AnalysisTaskAdapter : BaseAdapter<ItemAnalysisTaskBinding, AnalysisTaskMod
if (data.status == AnalysisTaskStatus.PROCESSING) {
binding.progressContainer.visibility = View.VISIBLE
binding.progressBar.progress = data.progress
binding.progressText.text = "${data.progress}%"
binding.progressText.text =
binding.root.context.getString(R.string.percentage_format, data.progress)
binding.progressText.setTextColor(DynamicColors.Primary)
} else {
binding.progressContainer.visibility = View.GONE
Expand Down Expand Up @@ -177,4 +178,4 @@ class AnalysisTaskAdapter : BaseAdapter<ItemAnalysisTaskBinding, AnalysisTaskMod
}


}
}
5 changes: 3 additions & 2 deletions app/src/main/java/net/ankio/auto/ui/adapter/AppAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package net.ankio.auto.ui.adapter

import android.content.pm.PackageManager
import net.ankio.auto.R
import net.ankio.auto.databinding.AdapterAppBinding
import net.ankio.auto.ui.api.BaseAdapter
import net.ankio.auto.ui.api.BaseViewHolder
Expand Down Expand Up @@ -114,7 +115,7 @@ class AppAdapter : BaseAdapter<AdapterAppBinding, AppInfo>() {
} catch (e: Exception) {
// 处理包信息获取异常,设置默认值
binding.appName.text = data.packageName
binding.appVersionName.text = "Unknown"
binding.appVersionName.text = binding.root.context.getString(R.string.unknown)
binding.appPackageName.text = data.packageName
binding.checkbox.isChecked = data.isSelected
}
Expand All @@ -129,4 +130,4 @@ class AppAdapter : BaseAdapter<AdapterAppBinding, AppInfo>() {
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class CategoryRuleAdapter(
if (!value) {
selectedIds.clear()
}
notifyDataSetChanged()
notifySelectionStateChanged()
}

/**
Expand Down Expand Up @@ -229,10 +229,16 @@ class CategoryRuleAdapter(
selectedIds.clear()
selectedIds.addAll(getItems().map { it.id })
}
notifyDataSetChanged()
notifySelectionStateChanged()
onSelectionChanged?.invoke(selectedIds.size)
}

private fun notifySelectionStateChanged() {
if (itemCount > 0) {
notifyItemRangeChanged(0, itemCount)
}
}

/**
* 移除选中的项目
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import net.ankio.auto.ui.api.BaseViewHolder
import net.ankio.auto.ui.utils.load
import org.ezbook.server.constant.Currency
import org.ezbook.server.db.model.CurrencyModel
import java.util.Locale

/**
* 币种选择适配器
Expand Down Expand Up @@ -118,7 +119,11 @@ class CurrencySelectorAdapter(
&& data.code != baseCurrencyCode
&& data.rate > 0
if (showRate) {
binding.rateText.text = "≈ ${formatRate(data.rate)} $baseCurrencyCode"
binding.rateText.text = context.getString(
R.string.approximate_rate_format,
formatRate(data.rate),
baseCurrencyCode
)
binding.rateText.visibility = View.VISIBLE
} else {
binding.rateText.visibility = View.GONE
Expand All @@ -127,9 +132,9 @@ class CurrencySelectorAdapter(

/** 格式化汇率数值:保留合理精度 */
private fun formatRate(rate: Double): String = when {
rate >= 100 -> String.format("%.0f", rate)
rate >= 1 -> String.format("%.2f", rate)
else -> String.format("%.4f", rate)
rate >= 100 -> String.format(Locale.getDefault(), "%.0f", rate)
rate >= 1 -> String.format(Locale.getDefault(), "%.2f", rate)
else -> String.format(Locale.getDefault(), "%.4f", rate)
}

override fun areItemsSame(oldItem: CurrencyModel, newItem: CurrencyModel): Boolean =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class TagSelectorAdapter(
this.selectedTags.addAll(
if (selectionLimit > 0) selectedTags.take(selectionLimit) else selectedTags
)
notifyDataSetChanged()
notifySelectionStateChanged()
}

companion object {
Expand Down Expand Up @@ -222,7 +222,13 @@ class TagSelectorAdapter(
fun clearSelection() {
if (isEditMode) return
selectedTags.clear()
notifyDataSetChanged()
notifySelectionStateChanged()
}

private fun notifySelectionStateChanged() {
if (itemCount > 0) {
notifyItemRangeChanged(0, itemCount)
}
}

override fun areItemsSame(oldItem: TagModel, newItem: TagModel): Boolean {
Expand Down
17 changes: 12 additions & 5 deletions app/src/main/java/net/ankio/auto/ui/api/BasePageFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import net.ankio.auto.storage.Logger
import net.ankio.auto.ui.components.StatusPage
import net.ankio.auto.ui.theme.DynamicColors
import net.ankio.auto.utils.CoroutineUtils.withIO
import kotlin.coroutines.cancellation.CancellationException

/**
* 基础分页Fragment抽象类
Expand Down Expand Up @@ -179,10 +180,10 @@ abstract class BasePageFragment<T, VB : ViewBinding> : BaseFragment<VB>() {
resetPage()
hasMoreData = true
statusPage.showLoading()

loadJob?.cancel()
isLoading = false

loadDataInside()
}

Expand All @@ -194,10 +195,10 @@ abstract class BasePageFragment<T, VB : ViewBinding> : BaseFragment<VB>() {
Logger.d("静默重新加载数据:从第一页开始")
resetPage()
hasMoreData = true

loadJob?.cancel()
isLoading = false

loadDataInside()
}

Expand All @@ -219,6 +220,7 @@ abstract class BasePageFragment<T, VB : ViewBinding> : BaseFragment<VB>() {
Logger.d("开始加载数据:第${page}页")

loadJob = launch {
val currentJob = coroutineContext[kotlinx.coroutines.Job]

try {
// 在IO线程中执行数据加载,避免阻塞UI
Expand All @@ -240,13 +242,18 @@ abstract class BasePageFragment<T, VB : ViewBinding> : BaseFragment<VB>() {
callback?.invoke(true, hasMoreData)
restoreScrollPosition()
}
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
if (page == 1) {
statusPage.showError()
}
callback?.invoke(false, hasMoreData)
} finally {
isLoading = false
if (loadJob === currentJob) {
isLoading = false
loadJob = null
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ class BreathingGradientView @JvmOverloads constructor(

init {
setWillNotDraw(false)
setupAnimator()
}

private fun setupAnimator() {
Expand All @@ -65,13 +64,16 @@ class BreathingGradientView @JvmOverloads constructor(
progress = it.animatedValue as Float
invalidate()
}
start()
}
}

// Start animation when attached to window
override fun onAttachedToWindow() {
super.onAttachedToWindow()
animator?.start()
if (animator == null) {
setupAnimator()
}
}

// Stop animation when detached from window
Expand Down Expand Up @@ -110,4 +112,4 @@ class BreathingGradientView @JvmOverloads constructor(
shader.setLocalMatrix(matrix)
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
}
}
}
Loading