Commit f0bf6ad87202da7ddf4bcf43414ea0bfbf048b26

Authored by 权海
1 parent 21c67bdd

feat(ui):添加apns权限请求,修正睡眠最早最晚算法

... ... @@ -338,6 +338,8 @@ interface PlatformHostApi {
* `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
*/
fun getFullUserAgent(): String
/** 申请通知权限 */
fun requestNotificationAuth(callback: (Result<Boolean>) -> Unit)
/** 是否是测试环境 */
fun isDebugEnvoriment(): Boolean
fun shareText(text: String, callback: (Result<Boolean>) -> Unit)
... ... @@ -417,6 +419,24 @@ interface PlatformHostApi {
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestNotificationAuth$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.requestNotificationAuth{ result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PlatformApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PlatformApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.isDebugEnvoriment$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
... ...
... ... @@ -368,6 +368,8 @@ protocol PlatformHostApi {
/// 返回完整的 User-Agent 字符串,由 native 侧组装:
/// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
func getFullUserAgent() throws -> String
/// 申请通知权限
func requestNotificationAuth(completion: @escaping (Result<Bool, Error>) -> Void)
/// 是否是测试环境
func isDebugEnvoriment() throws -> Bool
func shareText(text: String, completion: @escaping (Result<Bool, Error>) -> Void)
... ... @@ -434,6 +436,22 @@ class PlatformHostApiSetup {
} else {
getFullUserAgentChannel.setMessageHandler(nil)
}
/// 申请通知权限
let requestNotificationAuthChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestNotificationAuth\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestNotificationAuthChannel.setMessageHandler { _, reply in
api.requestNotificationAuth { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestNotificationAuthChannel.setMessageHandler(nil)
}
/// 是否是测试环境
let isDebugEnvorimentChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.isDebugEnvoriment\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
... ...
... ... @@ -903,11 +903,9 @@ class LocalHealthDataConvert {
List<HealthRawSleepResult> sleepResults, {
required Iterable<DateTime> allowedSleepDays,
}) {
final bedtime = _extremeBedtime(
_bedtimesBySleepDay(
final bedtime = _extremeSleepResultBedtime(
_sleepResultBedtimes(
sleepResults,
(item) => item.startDate,
earliest: true,
allowedSleepDays: allowedSleepDays,
),
earliest: true,
... ... @@ -923,11 +921,9 @@ class LocalHealthDataConvert {
List<HealthRawSleepResult> sleepResults, {
required Iterable<DateTime> allowedSleepDays,
}) {
final bedtime = _extremeBedtime(
_bedtimesBySleepDay(
final bedtime = _extremeSleepResultBedtime(
_sleepResultBedtimes(
sleepResults,
(item) => item.startDate,
earliest: false,
allowedSleepDays: allowedSleepDays,
),
earliest: false,
... ... @@ -939,6 +935,41 @@ class LocalHealthDataConvert {
);
}
static List<_SleepResultBedtime> _sleepResultBedtimes(
Iterable<HealthRawSleepResult> sleepResults, {
required Iterable<DateTime> allowedSleepDays,
}) {
final allowedSleepDayKeys = {
for (final day in allowedSleepDays) dateKey(day),
};
return [
for (final result in sleepResults)
if (allowedSleepDayKeys.contains(dateKey(dayOf(result.date))))
_SleepResultBedtime(
timestamp: result.startDate,
sleepDay: dayOf(result.date),
sleepStartOffset:
result.startDate - unixSeconds(dayOf(result.date)),
),
];
}
static _SleepResultBedtime? _extremeSleepResultBedtime(
Iterable<_SleepResultBedtime> bedtimes, {
required bool earliest,
}) {
_SleepResultBedtime? selected;
for (final bedtime in bedtimes) {
if (selected == null ||
(earliest
? bedtime.sleepStartOffset < selected.sleepStartOffset
: bedtime.sleepStartOffset > selected.sleepStartOffset)) {
selected = bedtime;
}
}
return selected;
}
static LatestSleepInfo? _latestSleepInfo(
List<HealthSleepDayCalculation> daily, {
required Iterable<DateTime> allowedSleepDays,
... ... @@ -1176,3 +1207,15 @@ class _SleepBedtime {
final DateTime sleepDay;
final int normalizedMinutes;
}
class _SleepResultBedtime {
const _SleepResultBedtime({
required this.timestamp,
required this.sleepDay,
required this.sleepStartOffset,
});
final int timestamp;
final DateTime sleepDay;
final int sleepStartOffset;
}
... ...
... ... @@ -393,6 +393,35 @@ class PlatformHostApi {
}
}
/// 申请通知权限
Future<bool> requestNotificationAuth() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestNotificationAuth$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as bool?)!;
}
}
/// 是否是测试环境
Future<bool> isDebugEnvoriment() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.isDebugEnvoriment$pigeonVar_messageChannelSuffix';
... ...
... ... @@ -116,6 +116,10 @@ abstract class PlatformHostApi {
/// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
String getFullUserAgent();
/// 申请通知权限
@async
bool requestNotificationAuth();
/// 是否是测试环境
bool isDebugEnvoriment();
... ...
... ... @@ -119,7 +119,7 @@ void main() {
expect(statistics.avgSleepEvaluate, 2);
});
test('compares weekly bedtime extremes by sleep-day clock time', () {
test('compares result bedtime extremes by sleep-end-day start offset', () {
final july27 = DateTime(2026, 7, 27);
final july28 = DateTime(2026, 7, 28);
final july29 = DateTime(2026, 7, 29);
... ... @@ -155,12 +155,12 @@ void main() {
statistics.earliestSleepInfo?.asleepTime,
LocalHealthDataConvert.unixSeconds(earliestBedtime),
);
expect(statistics.earliestSleepInfo?.timeKey, 20260728);
expect(statistics.earliestSleepInfo?.timeKey, 20260729);
expect(
statistics.latestSleepInfo?.asleepTime,
LocalHealthDataConvert.unixSeconds(latestBedtime),
);
expect(statistics.latestSleepInfo?.timeKey, 20260727);
expect(statistics.latestSleepInfo?.timeKey, 20260728);
});
test('uses the first and last records within a sleep day', () {
... ... @@ -201,11 +201,11 @@ void main() {
statistics.latestSleepInfo?.asleepTime,
LocalHealthDataConvert.unixSeconds(continuedSleep),
);
expect(statistics.earliestSleepInfo?.timeKey, 20260728);
expect(statistics.latestSleepInfo?.timeKey, 20260728);
expect(statistics.earliestSleepInfo?.timeKey, 20260729);
expect(statistics.latestSleepInfo?.timeKey, 20260729);
});
test('uses noon as the sleep day cutoff', () {
test('keeps negative sleep start offset from sleep end day', () {
final july28 = DateTime(2026, 7, 28);
final july29 = DateTime(2026, 7, 29);
final july30 = DateTime(2026, 7, 30);
... ... @@ -242,15 +242,15 @@ void main() {
statistics.earliestSleepInfo?.asleepTime,
LocalHealthDataConvert.unixSeconds(atNoon),
);
expect(statistics.earliestSleepInfo?.timeKey, 20260729);
expect(statistics.earliestSleepInfo?.timeKey, 20260730);
expect(
statistics.latestSleepInfo?.asleepTime,
LocalHealthDataConvert.unixSeconds(beforeNoon),
);
expect(statistics.latestSleepInfo?.timeKey, 20260728);
expect(statistics.latestSleepInfo?.timeKey, 20260729);
});
test('excludes records outside the report sleep-day range', () {
test('keeps records when sleep end day is inside the report range', () {
final july29 = DateTime(2026, 7, 29);
final overnightStart = DateTime(2026, 7, 29, 3);
final result = HealthRawSleepResult(
... ... @@ -274,8 +274,10 @@ void main() {
heartRate: const [],
);
expect(statistics.earliestSleepInfo, isNull);
expect(statistics.latestSleepInfo, isNull);
expect(statistics.earliestSleepInfo?.asleepTime, result.startDate);
expect(statistics.latestSleepInfo?.asleepTime, result.startDate);
expect(statistics.earliestSleepInfo?.timeKey, 20260729);
expect(statistics.latestSleepInfo?.timeKey, 20260729);
});
test('sleep duration excludes awake intervals inside sleep range', () {
... ...