feat(android): 支持沉浸式状态栏、地图定位与自定义签名
- 配置 edge-to-edge 沉浸式状态栏与刘海屏适配 - 新增百度地图权限、AK 配置及定位权限说明 - 接入 MethodChannel 实现返回键退后台热启动优化 - 配置自定义签名并更新构建逻辑 - 完善 token 刷新与鉴权失效统一处理 - 新增地图页与截图页路由 - 修复返回键拦截导致的 PopScope 失效问题
This commit is contained in:
50
lib/core/utils/coord_util.dart
Normal file
50
lib/core/utils/coord_util.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
|
||||
|
||||
/// WGS84(GPS) 经纬度转百度 BD09LL 坐标。
|
||||
///
|
||||
/// 分两步:WGS84 -> GCJ02(国测局火星坐标),再 GCJ02 -> BD09。
|
||||
/// 设备上报的定位通常为 GPS(WGS84),而百度地图使用 BD09LL 坐标系,
|
||||
/// 展示前必须做该转换,否则地图上位置会有偏移。
|
||||
BMFCoordinate wgs84ToBd09(double lat, double lon) {
|
||||
const double a = 6378245.0;
|
||||
const double ee = 0.00669342162296594323;
|
||||
|
||||
double transformLat(double x, double y) {
|
||||
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y +
|
||||
0.2 * sqrt(x.abs());
|
||||
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||
ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
|
||||
ret += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
double transformLon(double x, double y) {
|
||||
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y +
|
||||
0.1 * sqrt(x.abs());
|
||||
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
|
||||
ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
|
||||
ret += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// WGS84 -> GCJ02
|
||||
final double dLat = transformLat(lon - 105.0, lat - 35.0);
|
||||
final double dLon = transformLon(lon - 105.0, lat - 35.0);
|
||||
final double radLat = lat / 180.0 * pi;
|
||||
final double magic = sin(radLat);
|
||||
final double sqrtMagic = sqrt(1 - ee * magic * magic);
|
||||
final double mgLat =
|
||||
lat + (dLat * 180.0) / ((a * (1 - ee)) / (sqrtMagic * sqrtMagic) * pi);
|
||||
final double mgLon =
|
||||
lon + (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);
|
||||
|
||||
// GCJ02 -> BD09
|
||||
final double z = sqrt(mgLon * mgLon + mgLat * mgLat) + 0.00002 * sin(mgLat * pi);
|
||||
final double theta = atan2(mgLat, mgLon) + 0.000003 * cos(mgLon * pi);
|
||||
final double bdLon = z * cos(theta) + 0.0065;
|
||||
final double bdLat = z * sin(theta) + 0.006;
|
||||
|
||||
return BMFCoordinate(bdLat, bdLon);
|
||||
}
|
||||
49
lib/core/utils/system_ui_util.dart
Normal file
49
lib/core/utils/system_ui_util.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// 系统 UI(状态栏 / 导航栏)适配工具。
|
||||
///
|
||||
/// 集中处理沉浸式状态栏、edge-to-edge 布局与刘海屏(Display Cutout)适配,
|
||||
/// 避免在业务页面中散落系统 UI 相关代码。
|
||||
class SystemUiUtil {
|
||||
const SystemUiUtil._();
|
||||
|
||||
/// 应用启动时统一配置系统 UI。
|
||||
///
|
||||
/// - 使用 edge-to-edge 布局:内容延伸到状态栏与导航栏区域。
|
||||
/// - 状态栏 / 导航栏采用半透明叠加,图标使用深色(适配浅色背景 UI)。
|
||||
static Future<void> configure() async {
|
||||
// edge-to-edge:App 内容铺满全屏,安全区由 Flutter 的 SafeArea / MediaQuery
|
||||
// 在布局层兜底,系统栏以半透明覆盖层呈现。
|
||||
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
|
||||
// 状态栏 / 导航栏图标亮度:本工程为浅色背景,使用深色图标。
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
// 状态栏图标(时间、电量等)为深色。
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarBrightness: Brightness.light,
|
||||
// 导航栏图标为深色,导航栏背景半透明以配合 edge-to-edge。
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
// 允许刘海屏内容延伸(Android 端会同步在 Manifest 配置 cutout 模式)。
|
||||
statusBarColor: Colors.transparent,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 将应用移动到后台(实现 Android 热启动优化)。
|
||||
///
|
||||
/// 调用原生端的 moveTaskToBack(false),避免 Activity 销毁。
|
||||
static Future<void> moveToBack() async {
|
||||
const channel = MethodChannel('com.ttstd.familycare/app');
|
||||
try {
|
||||
await channel.invokeMethod('moveTaskToBack');
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('Failed to move task to back: ${e.message}');
|
||||
// 兜底方案:退回到系统默认行为
|
||||
await SystemNavigator.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
57
lib/core/utils/time_format.dart
Normal file
57
lib/core/utils/time_format.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
/// 统一的时间格式化工具。
|
||||
///
|
||||
/// 后端各时间字段(如截图 `uploadTime`、设备 `lastUpdateTime` 等)常以
|
||||
/// 原始字符串返回,格式不一(ISO8601 / 空格分隔 / 纯数字时间戳等)。
|
||||
/// 通过 [formatTimeString] 统一解析并格式化为友好的 `MM-dd HH:mm`。
|
||||
library;
|
||||
|
||||
/// 将后端返回的时间字符串解析为友好显示(`MM-dd HH:mm`)。
|
||||
///
|
||||
/// 支持以下常见格式:
|
||||
/// - ISO8601:`2026-08-18T14:30:00`、`2026-08-18T14:30:00+08:00`
|
||||
/// - 空格分隔:`2026-08-18 14:30:00`
|
||||
/// - 纯数字时间戳(秒或毫秒):`1784500200000`
|
||||
///
|
||||
/// 解析失败时返回 [fallback](默认空串);[raw] 为 null/空 时同样返回
|
||||
/// [fallback]。若无法识别但仍非空,会原样返回原始字符串。
|
||||
String formatTimeString(String? raw, {String fallback = ''}) {
|
||||
if (raw == null || raw.isEmpty) return fallback;
|
||||
|
||||
final dt = tryParseTimeString(raw);
|
||||
if (dt == null) return raw;
|
||||
|
||||
return formatDateTime(dt);
|
||||
}
|
||||
|
||||
/// 将 [DateTime] 格式化为友好的 `MM-dd HH:mm`。
|
||||
String formatDateTime(DateTime time) {
|
||||
final local = time.toLocal();
|
||||
final m = _two(local.month);
|
||||
final d = _two(local.day);
|
||||
final h = _two(local.hour);
|
||||
final min = _two(local.minute);
|
||||
return '$m-$d $h:$min';
|
||||
}
|
||||
|
||||
/// 尝试把后端常见的时间字符串解析为 [DateTime],失败返回 null。
|
||||
DateTime? tryParseTimeString(String raw) {
|
||||
final s = raw.trim();
|
||||
if (s.isEmpty) return null;
|
||||
|
||||
// 1. 纯数字:可能是秒或毫秒时间戳。
|
||||
if (RegExp(r'^\d+$').hasMatch(s)) {
|
||||
final n = int.tryParse(s);
|
||||
if (n != null) {
|
||||
// 13 位以上按毫秒,否则按秒。
|
||||
return n >= 1000000000000
|
||||
? DateTime.fromMillisecondsSinceEpoch(n)
|
||||
: DateTime.fromMillisecondsSinceEpoch(n * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 空格分隔:`2026-08-18 14:30:00` → 补 `T` 变 ISO8601。
|
||||
final iso = s.contains(' ') ? s.replaceFirst(' ', 'T') : s;
|
||||
return DateTime.tryParse(iso);
|
||||
}
|
||||
|
||||
String _two(int v) => v.toString().padLeft(2, '0');
|
||||
Reference in New Issue
Block a user