release: 发布6.6.18版本,完成多项合规与功能优化
此版本包含以下核心更新: 1. 版本号升级至6.6.18,更新全平台配置文件 2. 实现隐私合规改造: - 新增剪贴板隐私守卫,未同意协议前禁止读取剪贴板 - 所有桌面小部件继承隐私感知基类,未同意协议时显示占位提示 - 移除自动剪贴板监控,改为用户主动触发 3. 新增Windows平台深色主题同步功能 4. 补全多语言默认句子翻译 5. 优化安卓快捷方式配置与小部件合规性 6. 修复macOS插件注册问题 7. 新增Windows安装脚本 8. 优化触觉反馈服务初始化时机
This commit is contained in:
@@ -255,6 +255,15 @@
|
||||
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- 移除 androidx.glance.appwidget 自启动组件,防止应用退出后自启动 -->
|
||||
<!-- 合规要求:未同意隐私政策前不得自启动,Glance库未使用但会被合并入Manifest -->
|
||||
<receiver
|
||||
android:name="androidx.glance.appwidget.GlanceAppWidgetReceiver"
|
||||
tools:node="remove" />
|
||||
<service
|
||||
android:name="androidx.glance.appwidget.GlanceAppWidgetService"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
// ============================================================
|
||||
// 闲言APP — Android主Activity
|
||||
// 创建时间: 2026-04-20
|
||||
// 更新时间: 2026-06-13
|
||||
// 更新时间: 2026-06-16
|
||||
// 作用: Flutter主入口,处理BLE广播 + 管理空间跳转 + 协议安全网 + 快捷方式
|
||||
// 上次更新: 添加shortcut MethodChannel,处理桌面快捷方式跳转
|
||||
// 保留插件移除/恢复逻辑作为安全网(防Shortcut等绕过SplashActivity)
|
||||
// 上次更新: 添加ShortcutManager快捷方式创建,增强快捷方式调试日志
|
||||
// ============================================================
|
||||
|
||||
package apps.xy.xianyan
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.content.pm.ShortcutManager
|
||||
import android.graphics.drawable.Icon
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import apps.xy.xianyan.ble.BleAdvertiserPlugin
|
||||
@@ -27,6 +30,7 @@ class MainActivity : FlutterActivity() {
|
||||
private const val BACKGROUND_CHANNEL = "apps.xy.xianyan/background"
|
||||
private const val AGREEMENT_CHANNEL = "apps.xy.xianyan/agreement"
|
||||
private const val SHORTCUT_CHANNEL = "apps.xy.xianyan/shortcut"
|
||||
private const val SHORTCUT_MANAGER_CHANNEL = "apps.xy.xianyan/shortcut_manager"
|
||||
private const val ACTION_MANAGE_STORAGE = "android.app.action.MANAGE_STORAGE"
|
||||
|
||||
// Intent extra key — 桌面快捷方式传递的action类型
|
||||
@@ -41,18 +45,45 @@ class MainActivity : FlutterActivity() {
|
||||
// 异常流程:Shortcut/DeepLink可能直接启动此Activity,此时仍需移除敏感插件
|
||||
// 注意:此安全网无法阻止onAttachedToActivity中的传感器读取(注册时已触发)
|
||||
// 完整防护依赖SplashActivity在引擎启动前拦截
|
||||
//
|
||||
// 分类说明:
|
||||
// 🔴 传感器类 — 读取设备传感器数据(加速度/陀螺仪/方向等)
|
||||
// 🔴 音频类 — 麦克风录音、音频播放
|
||||
// 🔴 位置类 — GPS/网络定位
|
||||
// 🔴 网络类 — WiFi信息、网络状态检测
|
||||
// 🔴 通知类 — 本地推送通知
|
||||
// 🔴 媒体类 — 相机/相册/视频播放
|
||||
// 🔴 生物识别类 — 指纹/面容
|
||||
// 🔴 震动类 — 触觉反馈
|
||||
private val SENSITIVE_PLUGIN_CLASSES = setOf(
|
||||
"dev.fluttercommunity.plus.sensors.SensorsPlugin", // 传感器(OrientationEventListener读取传感器列表)
|
||||
"com.llfbandit.record.RecordPlugin", // 麦克风录音
|
||||
"com.csdcorp.speech_to_text.SpeechToTextPlugin", // 语音识别
|
||||
"com.lyokone.location.LocationPlugin", // 位置
|
||||
"com.alternadom.wifiiot.WifiIotPlugin", // WiFi
|
||||
"dev.fluttercommunity.plus.connectivity.ConnectivityPlugin", // 网络状态
|
||||
"com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin", // 通知
|
||||
"io.flutter.plugins.imagepicker.ImagePickerPlugin", // 相机/相册
|
||||
"io.flutter.plugins.localauth.LocalAuthPlugin", // 生物识别
|
||||
"com.fluttercandia.photo_manager.PhotoManagerPlugin", // 相册
|
||||
"io.flutter.plugins.videoplayer.VideoPlayerPlugin", // 视频
|
||||
// 🔴 传感器
|
||||
"dev.fluttercommunity.plus.sensors.SensorsPlugin",
|
||||
// 🔴 麦克风录音
|
||||
"com.llfbandit.record.RecordPlugin",
|
||||
// 🔴 语音识别
|
||||
"com.csdcorp.speech_to_text.SpeechToTextPlugin",
|
||||
// 🔴 位置
|
||||
"com.lyokone.location.LocationPlugin",
|
||||
// 🔴 WiFi信息
|
||||
"com.alternadom.wifiiot.WifiIotPlugin",
|
||||
// 🔴 网络状态
|
||||
"dev.fluttercommunity.plus.connectivity.ConnectivityPlugin",
|
||||
// 🔴 WiFi网络信息
|
||||
"dev.fluttercommunity.plus.network_info.NetworkInfoPlugin",
|
||||
// 🔴 通知
|
||||
"com.dexterous.flutterlocalnotifications.FlutterLocalNotificationsPlugin",
|
||||
// 🔴 相机/相册
|
||||
"io.flutter.plugins.imagepicker.ImagePickerPlugin",
|
||||
// 🔴 生物识别
|
||||
"io.flutter.plugins.localauth.LocalAuthPlugin",
|
||||
// 🔴 相册管理
|
||||
"com.fluttercandia.photo_manager.PhotoManagerPlugin",
|
||||
// 🔴 视频播放
|
||||
"io.flutter.plugins.videoplayer.VideoPlayerPlugin",
|
||||
// 🔴 音频播放
|
||||
"xyz.luan.audioplayers.AudioplayersPlugin",
|
||||
// 🔴 震动/触觉反馈
|
||||
"com.whelksoft.flutter_vibrate.FlutterVibratePlugin",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -73,6 +104,7 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.i(TAG, "MainActivity.onCreate")
|
||||
bleAdvertiser.setupChannel(flutterEngine!!, this)
|
||||
handleManageSpaceIntent(intent)
|
||||
handleShortcutIntent(intent)
|
||||
@@ -80,6 +112,7 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.i(TAG, "MainActivity.onNewIntent: $intent")
|
||||
setIntent(intent)
|
||||
handleManageSpaceIntent(intent)
|
||||
handleShortcutIntent(intent)
|
||||
@@ -87,12 +120,14 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
Log.i(TAG, "MainActivity.onResume: pendingShortcutAction=$pendingShortcutAction")
|
||||
pendingManageSpaceAction?.let { action ->
|
||||
pendingManageSpaceAction = null
|
||||
window.decorView.post { invokeFlutterMethod(action) }
|
||||
}
|
||||
pendingShortcutAction?.let { action ->
|
||||
pendingShortcutAction = null
|
||||
Log.i(TAG, "MainActivity.onResume: 处理快捷方式 $action")
|
||||
window.decorView.post { invokeShortcutAction(action) }
|
||||
}
|
||||
}
|
||||
@@ -246,6 +281,84 @@ class MainActivity : FlutterActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- ShortcutManager MethodChannel(快捷方式创建和图标设置)----
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
SHORTCUT_MANAGER_CHANNEL
|
||||
).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"createShortcuts" -> {
|
||||
// 从Flutter端调用,创建桌面快捷方式
|
||||
try {
|
||||
createShortcuts()
|
||||
result.success(true)
|
||||
Log.i(TAG, "ShortcutManager: 快捷方式创建成功")
|
||||
} catch (e: Exception) {
|
||||
result.error("SHORTCUT_ERROR", "创建快捷方式失败: ${e.message}", null)
|
||||
Log.e(TAG, "ShortcutManager: 创建快捷方式失败", e)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "ShortcutManager: MethodChannel已注册")
|
||||
} else {
|
||||
Log.i(TAG, "ShortcutManager: API版本不支持(${Build.VERSION.SDK_INT}),跳过")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用ShortcutManager API创建桌面快捷方式
|
||||
* API 25+ 支持,使用应用图标作为快捷方式图标
|
||||
*/
|
||||
private fun createShortcuts() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
|
||||
Log.w(TAG, "ShortcutManager: API < 25,不支持动态快捷方式")
|
||||
return
|
||||
}
|
||||
|
||||
val shortcutManager = getSystemService(ShortcutManager::class.java)
|
||||
if (!shortcutManager.isRequestPinShortcutSupported) {
|
||||
Log.w(TAG, "ShortcutManager: 设备不支持快捷方式固定")
|
||||
return
|
||||
}
|
||||
|
||||
val themeIntent = Intent(this, MainActivity::class.java).apply {
|
||||
action = Intent.ACTION_RUN
|
||||
putExtra(EXTRA_SHORTCUT_ACTION, "action_theme")
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
|
||||
val searchIntent = Intent(this, MainActivity::class.java).apply {
|
||||
action = Intent.ACTION_RUN
|
||||
putExtra(EXTRA_SHORTCUT_ACTION, "action_search")
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
}
|
||||
|
||||
// 使用drawable资源作为快捷方式图标(矢量图标)
|
||||
val themeIcon = Icon.createWithResource(this, R.drawable.ic_shortcut_theme)
|
||||
val searchIcon = Icon.createWithResource(this, R.drawable.ic_shortcut_search)
|
||||
|
||||
val shortcuts = listOf(
|
||||
ShortcutInfo.Builder(this, "action_theme")
|
||||
.setShortLabel("主题个性化")
|
||||
.setLongLabel("打开主题个性化设置")
|
||||
.setIcon(themeIcon)
|
||||
.setIntent(themeIntent)
|
||||
.build(),
|
||||
ShortcutInfo.Builder(this, "action_search")
|
||||
.setShortLabel("搜索功能")
|
||||
.setLongLabel("搜索APP内功能")
|
||||
.setIcon(searchIcon)
|
||||
.setIntent(searchIntent)
|
||||
.build()
|
||||
)
|
||||
|
||||
// 使用setDynamicShortcuts设置动态快捷方式
|
||||
shortcutManager.setDynamicShortcuts(shortcuts)
|
||||
Log.i(TAG, "ShortcutManager: 已设置 ${shortcuts.size} 个动态快捷方式")
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -290,13 +403,26 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
/**
|
||||
* 处理桌面快捷方式Intent
|
||||
* 检测intent中是否包含shortcut_action extra,如果有则通过MethodChannel通知Flutter端
|
||||
* 检测intent中是否包含shortcut_action extra或xianyan://协议,
|
||||
* 如果有则通过MethodChannel通知Flutter端
|
||||
*/
|
||||
private fun handleShortcutIntent(intent: Intent?) {
|
||||
if (intent == null) return
|
||||
|
||||
// 从intent extras中读取shortcut_action
|
||||
val shortcutAction = intent.getStringExtra(EXTRA_SHORTCUT_ACTION)
|
||||
// 优先从intent extras中读取shortcut_action(动态快捷方式)
|
||||
var shortcutAction = intent.getStringExtra(EXTRA_SHORTCUT_ACTION)
|
||||
|
||||
// 如果没有extra,检查intent.data(静态快捷方式使用xianyan://协议)
|
||||
if (shortcutAction == null && intent.data != null) {
|
||||
val uri = intent.data.toString()
|
||||
Log.i(TAG, "handleShortcutIntent: 检查intent.data: $uri")
|
||||
// xianyan://shortcut/action_theme -> 提取 action_theme
|
||||
if (uri.startsWith("xianyan://shortcut/")) {
|
||||
shortcutAction = uri.removePrefix("xianyan://shortcut/")
|
||||
Log.i(TAG, "handleShortcutIntent: 从data URI提取shortcutAction: $shortcutAction")
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcutAction != null) {
|
||||
Log.i(TAG, "handleShortcutIntent: 收到快捷方式action: $shortcutAction")
|
||||
pendingShortcutAction = shortcutAction
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// ============================================================
|
||||
// 闲言APP — 启动页Activity(隐私协议守门人)
|
||||
// 创建时间: 2026-06-10
|
||||
// 更新时间: 2026-06-13
|
||||
// 更新时间: 2026-06-16
|
||||
// 作用: 应用启动入口,在用户同意隐私政策前阻止Flutter引擎启动
|
||||
// 从根本上防止SensorsPlugin等敏感插件在协议前读取传感器
|
||||
// 上次更新: 修复overridePendingTransition弃用警告,兼容Android 14+新API
|
||||
// 上次更新: 支持快捷方式action转发,确保快捷方式通过SplashActivity
|
||||
// 不绕过协议守门,同时正确传递shortcut_action到MainActivity
|
||||
// ============================================================
|
||||
// 设计说明:
|
||||
// 此Activity是应用的LAUNCHER入口,在AndroidManifest中声明。
|
||||
@@ -16,6 +17,11 @@
|
||||
// 5. 用户同意 → 持久化状态,启动MainActivity
|
||||
// 6. 用户拒绝 → 退出应用
|
||||
//
|
||||
// 快捷方式流程:
|
||||
// shortcuts.xml中targetClass指向SplashActivity
|
||||
// SplashActivity读取shortcut_action extra并转发给MainActivity
|
||||
// 确保协议守门不被绕过
|
||||
//
|
||||
// 关键:MainActivity(Flutter引擎)只有在用户同意后才被启动,
|
||||
// 因此GeneratedPluginRegistrant.registerWith()中的SensorsPlugin
|
||||
// 等敏感插件不会在协议前被注册和触发onAttachedToActivity。
|
||||
@@ -24,21 +30,26 @@
|
||||
package apps.xy.xianyan
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.net.toUri
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.io.File
|
||||
|
||||
@Suppress(
|
||||
"unused", // TAG 常量保留供日志使用
|
||||
"DEPRECATION" // overridePendingTransition 已有兼容处理
|
||||
)
|
||||
class SplashActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SplashActivity"
|
||||
|
||||
// 原生SharedPreferences — 与MainActivity共用
|
||||
private const val PREFS_NAME = "xianyan_prefs"
|
||||
private const val KEY_AGREEMENT_ACCEPTED = "agreement_accepted"
|
||||
@@ -54,16 +65,14 @@ class SplashActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
|
||||
val hasAgreed = prefs.getBoolean(KEY_AGREEMENT_ACCEPTED, false)
|
||||
|
||||
when {
|
||||
// 已同意协议 → 直接进入Flutter
|
||||
hasAgreed -> {
|
||||
prefs.getBoolean(KEY_AGREEMENT_ACCEPTED, false) -> {
|
||||
startMainActivity()
|
||||
}
|
||||
// 老用户升级(有Flutter数据但未设置原生协议标志)→ 自动迁移
|
||||
isExistingUser() -> {
|
||||
prefs.edit().putBoolean(KEY_AGREEMENT_ACCEPTED, true).apply()
|
||||
prefs.edit { putBoolean(KEY_AGREEMENT_ACCEPTED, true) }
|
||||
startMainActivity()
|
||||
}
|
||||
// 新用户 → 显示隐私协议对话框
|
||||
@@ -73,6 +82,16 @@ class SplashActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 快捷方式action读取 ----
|
||||
|
||||
/**
|
||||
* 从Intent中读取快捷方式action
|
||||
* shortcuts.xml中的shortcut通过extra传递action
|
||||
*/
|
||||
private fun getShortcutAction(): String? {
|
||||
return intent?.getStringExtra("shortcut_action")
|
||||
}
|
||||
|
||||
// ---- 老用户检测 ----
|
||||
|
||||
/**
|
||||
@@ -162,7 +181,9 @@ class SplashActivity : AppCompatActivity() {
|
||||
"• 🎤 麦克风 — 用于语音输入和录音\n" +
|
||||
"• 👆 生物识别 — 用于应用锁和隐私保护\n" +
|
||||
"• 📡 WiFi — 用于局域网文件传输\n" +
|
||||
"• 🎵 音频/视频 — 用于媒体播放和编辑"
|
||||
"• 🎵 音频/视频 — 用于媒体播放和编辑\n" +
|
||||
"• 📋 剪贴板 — 用于用户主动粘贴/分享内容(仅用户操作时读取)\n" +
|
||||
"• 📐 传感器 — 用于摇一摇等交互功能(仅用户触发时使用)"
|
||||
}
|
||||
|
||||
// ---- 协议同意/拒绝处理 ----
|
||||
@@ -173,9 +194,7 @@ class SplashActivity : AppCompatActivity() {
|
||||
*/
|
||||
private fun onAgreementAccepted() {
|
||||
getSharedPreferences(PREFS_NAME, MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(KEY_AGREEMENT_ACCEPTED, true)
|
||||
.apply()
|
||||
.edit { putBoolean(KEY_AGREEMENT_ACCEPTED, true) }
|
||||
startMainActivity()
|
||||
}
|
||||
|
||||
@@ -192,9 +211,17 @@ class SplashActivity : AppCompatActivity() {
|
||||
/**
|
||||
* 启动Flutter主Activity
|
||||
* 使用无动画过渡,保持启动页视觉连续性
|
||||
* 同时转发快捷方式action(如果有)
|
||||
* 如果MainActivity已在运行(热启动),复用现有实例并传递action
|
||||
*/
|
||||
private fun startMainActivity() {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
// 转发快捷方式action到MainActivity
|
||||
getShortcutAction()?.let { action ->
|
||||
intent.putExtra("shortcut_action", action)
|
||||
}
|
||||
// 如果MainActivity已在任务栈中,复用实例而非创建新实例
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
startActivity(intent)
|
||||
overrideTransitionCompat(0, 0)
|
||||
finish()
|
||||
@@ -218,12 +245,13 @@ class SplashActivity : AppCompatActivity() {
|
||||
|
||||
/**
|
||||
* 在浏览器中打开URL
|
||||
* 使用 createChooser 确保走外部浏览器,避免匹配应用自身的 Deep Link
|
||||
*/
|
||||
private fun openUrl(url: String) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
startActivity(Intent.createChooser(intent, null))
|
||||
} catch (_: Exception) {
|
||||
// 无浏览器可用,忽略
|
||||
}
|
||||
}
|
||||
@@ -231,16 +259,17 @@ class SplashActivity : AppCompatActivity() {
|
||||
/**
|
||||
* 暗色模式适配:调整自定义布局中的文字颜色
|
||||
*/
|
||||
@Suppress("NewApi") // minSdk=28,实际不会触发
|
||||
private fun adaptDarkMode(root: View) {
|
||||
val isDark = resources.configuration.uiMode and
|
||||
android.content.res.Configuration.UI_MODE_NIGHT_MASK ==
|
||||
android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
Configuration.UI_MODE_NIGHT_MASK ==
|
||||
Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
if (!isDark) return
|
||||
|
||||
val titleColor = android.graphics.Color.parseColor("#E0E0E0")
|
||||
val bodyColor = android.graphics.Color.parseColor("#B0B0B0")
|
||||
val linkColor = android.graphics.Color.parseColor("#90CAF9")
|
||||
val titleColor = "#E0E0E0".toColorInt()
|
||||
val bodyColor = "#B0B0B0".toColorInt()
|
||||
val linkColor = "#90CAF9".toColorInt()
|
||||
|
||||
adjustColors(root, titleColor, bodyColor, linkColor)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* CheckinProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 每日签到桌面小部件Provider
|
||||
* 作用: 在Android桌面展示连续签到天数和快捷签到入口,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class CheckinProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class CheckinProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* CountdownProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 倒计时桌面小部件Provider
|
||||
* 作用: 在Android桌面展示自定义倒计时事件天数,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,12 +15,11 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
import java.time.LocalDate
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class CountdownProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class CountdownProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* CtcLatestNoteProvider.kt
|
||||
* 创建时间: 2026-06-15
|
||||
* 更新时间: 2026-06-15
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: CTC最新笔记桌面小部件Provider
|
||||
* 作用: 在Android桌面展示CTC最新笔记的钥匙名和内容预览,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局,点击跳转/ctc路由
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class CtcLatestNoteProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class CtcLatestNoteProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* DailyCardProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 日签卡片桌面小部件Provider
|
||||
* 作用: 在Android桌面展示精美日签卡片,含日期、句子和作者,支持深色主题
|
||||
* 上次更新内容: 新增dark主题支持,根据widget_theme_mode切换布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class DailyCardProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class DailyCardProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* DailySentenceProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 每日一句桌面小部件Provider
|
||||
* 作用: 在Android桌面展示每日精选句子和作者,支持深色主题
|
||||
* 上次更新内容: 新增dark主题支持,根据widget_theme_mode切换布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class DailySentenceProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class DailySentenceProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* FortuneProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 每日运势桌面小部件Provider
|
||||
* 作用: 在Android桌面展示每日运势和幸运关键词,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class FortuneProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class FortuneProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* PomodoroProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 番茄钟桌面小部件Provider
|
||||
* 作用: 在Android桌面展示番茄钟倒计时和状态,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class PomodoroProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class PomodoroProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* PrivacyAwareHomeWidgetProvider.kt
|
||||
* 创建时间: 2026-06-17
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 隐私协议感知的桌面小部件基类
|
||||
* 作用: 所有Widget Provider的基类,在用户未同意隐私政策前阻止Widget更新,
|
||||
* 防止应用退出后被系统广播触发自启动导致隐私合规问题
|
||||
* 上次更新内容: 初始创建,基于HomeWidgetProvider增加隐私协议守门逻辑
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
/**
|
||||
* 隐私协议感知的桌面小部件基类
|
||||
*
|
||||
* 核心设计:
|
||||
* - 在 onUpdate 中检查原生 SharedPreferences 的 agreement_accepted 标志
|
||||
* - 未同意隐私政策时,显示"请先同意隐私政策"占位视图,不读取任何业务数据
|
||||
* - 已同意隐私政策时,调用子类实现的 [onUpdateWithAgreement] 执行正常更新
|
||||
*
|
||||
* 合规依据:
|
||||
* - 《个人信息保护法》要求:处理个人信息前需取得个人同意
|
||||
* - 应用商店审核要求:未同意隐私政策前不得自启动或读取用户数据
|
||||
* - androidx.glance.appwidget 自启动问题修复:即使系统触发 APPWIDGET_UPDATE,
|
||||
* 未同意协议也不执行任何数据操作
|
||||
*/
|
||||
abstract class PrivacyAwareHomeWidgetProvider : HomeWidgetProvider() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PrivacyWidget"
|
||||
// 与 SplashActivity / MainActivity 共用的原生 SharedPreferences
|
||||
private const val PREFS_NAME = "xianyan_prefs"
|
||||
private const val KEY_AGREEMENT_ACCEPTED = "agreement_accepted"
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已同意隐私政策
|
||||
* 读取原生 SharedPreferences,与 SplashActivity 使用同一存储
|
||||
*/
|
||||
private fun isAgreementAccepted(context: Context): Boolean {
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
return prefs.getBoolean(KEY_AGREEMENT_ACCEPTED, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* HomeWidgetProvider 的 onUpdate 入口
|
||||
* 在此进行隐私协议检查,决定是否执行业务更新
|
||||
*/
|
||||
final override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
) {
|
||||
if (!isAgreementAccepted(context)) {
|
||||
// 未同意隐私政策 — 显示占位视图,不读取任何业务数据
|
||||
Log.w(TAG, "${this::class.simpleName}: 隐私政策未同意,显示占位视图")
|
||||
showPrivacyPlaceholder(context, appWidgetManager, appWidgetIds)
|
||||
return
|
||||
}
|
||||
|
||||
// 已同意隐私政策 — 执行子类的正常更新逻辑
|
||||
onUpdateWithAgreement(context, appWidgetManager, appWidgetIds, widgetData)
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示隐私政策未同意时的占位视图
|
||||
* 提示用户需先打开应用同意隐私政策,点击可跳转到 SplashActivity
|
||||
*/
|
||||
private fun showPrivacyPlaceholder(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
appWidgetIds.forEach { widgetId ->
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_privacy_placeholder).apply {
|
||||
// 点击跳转到应用(通过 SplashActivity 的 LAUNCHER intent)
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
if (launchIntent != null) {
|
||||
val pendingIntent = android.app.PendingIntent.getActivity(
|
||||
context,
|
||||
widgetId,
|
||||
launchIntent,
|
||||
android.app.PendingIntent.FLAG_UPDATE_CURRENT or android.app.PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
setOnClickPendingIntent(R.id.widget_privacy_container, pendingIntent)
|
||||
}
|
||||
}
|
||||
appWidgetManager.updateAppWidget(widgetId, views)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 子类实现的更新逻辑,仅在用户已同意隐私政策后调用
|
||||
* 与 HomeWidgetProvider.onUpdate 签名一致
|
||||
*/
|
||||
abstract fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* ReadlaterProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 稍后读桌面小部件Provider
|
||||
* 作用: 在Android桌面展示稍后读未读数量和预览,支持深色主题
|
||||
* 上次更新内容: 新增dark主题支持,根据widget_theme_mode切换布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class ReadlaterProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class ReadlaterProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* SolarTermProvider.kt
|
||||
* 创建时间: 2026-05-19
|
||||
* 更新时间: 2026-05-19
|
||||
* 更新时间: 2026-06-17
|
||||
* 名称: 节气诗词桌面小部件Provider
|
||||
* 作用: 在Android桌面展示当前节气与对应诗词,支持深色主题
|
||||
* 上次更新内容: 初始创建,支持light/dark双主题布局
|
||||
* 上次更新内容: 改为继承PrivacyAwareHomeWidgetProvider,未同意隐私政策时不执行更新
|
||||
*/
|
||||
package apps.xy.xianyan.widget
|
||||
|
||||
@@ -15,10 +15,9 @@ import android.widget.RemoteViews
|
||||
import apps.xy.xianyan.R
|
||||
import apps.xy.xianyan.MainActivity
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class SolarTermProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
class SolarTermProvider : PrivacyAwareHomeWidgetProvider() {
|
||||
override fun onUpdateWithAgreement(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- widget_privacy_placeholder.xml -->
|
||||
<!-- 隐私政策未同意时的Widget占位布局 -->
|
||||
<!-- 合规要求:未同意隐私政策前Widget不得展示用户数据 -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_privacy_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#F5F5F5"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🔒"
|
||||
android:textSize="24sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="请先同意隐私政策"
|
||||
android:textColor="#666666"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="点击打开应用"
|
||||
android:textColor="#999999"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_shortcut.png
Normal file
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_shortcut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_shortcut.png
Normal file
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_shortcut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_shortcut.png
Normal file
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_shortcut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_shortcut.png
Normal file
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_shortcut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_shortcut.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_shortcut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -8,6 +8,7 @@
|
||||
<string name="widget_pomodoro_desc">桌面快捷专注计时</string>
|
||||
<string name="widget_solar_term_desc">当前节气与对应诗词</string>
|
||||
<string name="widget_checkin_desc">连续签到天数和快捷签到</string>
|
||||
<string name="widget_ctc_latest_note_desc">最新笔记内容预览</string>
|
||||
<string name="data_management_label">闲言数据管理</string>
|
||||
<string name="shortcut_theme_short">主题个性化</string>
|
||||
<string name="shortcut_theme_long">打开主题个性化设置</string>
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
<!-- ============================================================
|
||||
闲言APP — 桌面快捷方式配置
|
||||
创建时间: 2026-06-09
|
||||
更新时间: 2026-06-13
|
||||
作用: 定义长按桌面图标显示的快捷方式
|
||||
上次更新: 使用矢量drawable替代mipmap图标,规范extra key名称
|
||||
更新时间: 2026-06-16
|
||||
作用: 定义长按桌面图标显示的快捷方式(API 25+ 静态定义)
|
||||
注意: 此文件定义静态快捷方式,由系统在应用安装时读取显示
|
||||
实际快捷方式处理由 MainActivity 通过 ShortcutManager API 管理
|
||||
============================================================ -->
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 主题个性化快捷方式 -->
|
||||
<shortcut
|
||||
android:shortcutId="action_theme"
|
||||
android:enabled="true"
|
||||
@@ -16,10 +18,11 @@
|
||||
<intent
|
||||
android:action="android.intent.action.RUN"
|
||||
android:targetPackage="apps.xy.xianyan"
|
||||
android:targetClass="apps.xy.xianyan.MainActivity">
|
||||
<extra android:name="shortcut_action" android:value="action_theme" />
|
||||
</intent>
|
||||
android:targetClass="apps.xy.xianyan.MainActivity"
|
||||
android:data="xianyan://shortcut/action_theme" />
|
||||
</shortcut>
|
||||
|
||||
<!-- 搜索功能快捷方式 -->
|
||||
<shortcut
|
||||
android:shortcutId="action_search"
|
||||
android:enabled="true"
|
||||
@@ -29,8 +32,7 @@
|
||||
<intent
|
||||
android:action="android.intent.action.RUN"
|
||||
android:targetPackage="apps.xy.xianyan"
|
||||
android:targetClass="apps.xy.xianyan.MainActivity">
|
||||
<extra android:name="shortcut_action" android:value="action_search" />
|
||||
</intent>
|
||||
android:targetClass="apps.xy.xianyan.MainActivity"
|
||||
android:data="xianyan://shortcut/action_search" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
|
||||
Reference in New Issue
Block a user