Commit 0c45ba2f740556d4e87681ac94d6cc6406f23ad8

Authored by 刘宏哲
1 parent f3ff05fc

feat(app): ohos cropper适配

import 'dart:async';
import 'dart:io';
import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_theme_models.dart';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
... ... @@ -7,15 +8,16 @@ import 'package:doublefeel_flutter/core/logging/app_logger.dart';
import 'package:doublefeel_flutter/core/network/api/theme_api.dart';
import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logger/logger.dart';
class MyController extends GetxController {
MyController(this._userApi, this._vipApi, this._themeApi);
... ... @@ -64,22 +66,45 @@ class MyController extends GetxController {
);
if (picked == null) return; // 用户取消
AppLogger.e(picked.path);
// 2. 用 image_cropper 裁剪为正方形
final CroppedFile? cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪头像',
lockAspectRatio: true,
),
IOSUiSettings(
title: '裁剪头像',
aspectRatioLockEnabled: true,
resetAspectRatioEnabled: false,
),
],
);
// OHOS 版 image_cropper 使用 Flutter 页面承载裁剪 UI,必须显式传入
// BuildContext(该插件目前通过 WebUiSettings 读取该参数)。
final isOhos = Platform.operatingSystem.toLowerCase() == 'ohos';
final cropperContext = Get.context ?? Get.overlayContext;
CroppedFile? cropped;
try {
cropped = await ImageCropper().cropImage(
sourcePath: picked.path,
aspectRatio: const CropAspectRatio(ratioX: 1, ratioY: 1),
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪头像',
lockAspectRatio: true,
),
IOSUiSettings(
title: '裁剪头像',
aspectRatioLockEnabled: true,
resetAspectRatioEnabled: false,
),
if (isOhos && cropperContext != null)
WebUiSettings(context: cropperContext),
],
);
} finally {
if (isOhos) {
// 等待 cropper 路由的退场动画结束,防止其 AppBar 覆盖恢复的样式。
await Future<void>.delayed(const Duration(milliseconds: 400));
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: Brightness.dark,
),
);
}
}
if (cropped == null) return; // 用户取消裁剪
final ossPath = await _platformHostApi.uploadFile(
... ...
... ... @@ -44,4 +44,12 @@ enum HuaweiHealthDataType {
const HuaweiHealthDataType(this.dataType);
final int dataType;
/// Resolves the server/native `data_type` value to a supported type.
static HuaweiHealthDataType? fromDataType(int dataType) {
for (final type in values) {
if (type.dataType == dataType) return type;
}
return null;
}
}
... ...
... ... @@ -2,6 +2,7 @@ import 'dart:async';
import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/health_raw_data_core_service.dart';
import 'package:doublefeel_flutter/core/services/raw_data_service/platform_ohos/huawei_health_data_type.dart';
import 'package:doublefeel_flutter/core/platform/pigeon_api_facade.dart';
import '../logging/app_logger.dart';
... ... @@ -14,6 +15,8 @@ import 'web_socket_service.dart';
/// Login lifecycle and SDK initialization.
class UserStateService {
static const _healthDataUpdateXTag = 'health_data_update';
UserStateService(
this._environmentConfig,
this._userPrefs,
... ... @@ -33,6 +36,7 @@ class UserStateService {
final ThinkingDataService _thinkingDataService;
final HealthRawDataCoreService _healthRawDataCoreService;
final WebSocketService _webSocketService;
StreamSubscription<WebSocketEvent>? _healthDataUpdateSubscription;
bool sdksInitialized = false;
... ... @@ -53,6 +57,7 @@ class UserStateService {
// after login, but must not delay the rest of the authenticated session.
unawaited(_pushService.registerPushToken());
await _imService.connect();
_listenForHealthDataUpdates();
_webSocketService.start();
}
... ... @@ -61,6 +66,8 @@ class UserStateService {
_pushService.cancelPushTokenRegistration();
_imService.disconnect();
_webSocketService.stop();
await _healthDataUpdateSubscription?.cancel();
_healthDataUpdateSubscription = null;
_healthRawDataCoreService.closeDatabase();
if (callServerLogout) {
await _userApi.logout();
... ... @@ -74,6 +81,45 @@ class UserStateService {
AppLogger.i('UserStateService.onLogout completed');
}
void _listenForHealthDataUpdates() {
_healthDataUpdateSubscription ??= _webSocketService
.eventsFor(_healthDataUpdateXTag)
.listen(_onHealthDataUpdate);
}
void _onHealthDataUpdate(WebSocketEvent event) {
final dataType = _healthDataTypeFromBody(event.body);
if (dataType == null) {
AppLogger.w(
'Ignored health_data_update with an invalid data_type: ${event.body}',
);
return;
}
final processing = _healthRawDataCoreService.onHealthDataUpdated(
dataTypes: <int>[dataType.dataType],
);
unawaited(processing.catchError((Object error, StackTrace stackTrace) {
AppLogger.w('Failed to process health_data_update', error, stackTrace);
}));
}
HuaweiHealthDataType? _healthDataTypeFromBody(Object? body) {
if (body is! Map) return null;
final value = body['data_type'];
final dataType = switch (value) {
final int number => number,
final double number
when number.isFinite && number == number.truncateToDouble() =>
number.toInt(),
final String text => int.tryParse(text),
_ => null,
};
return dataType == null
? null
: HuaweiHealthDataType.fromDataType(dataType);
}
void _trackLogin() {
final uid = _userPrefs.preferences.value.meUserInfo?.id ?? 0;
if (uid > 0) {
... ...
... ... @@ -451,7 +451,9 @@ export class PlatformHostApiImpl extends PlatformHostApi {
id: notificationId,
content: {
notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: { title, text: content, additionalText: link },
// The deep link is carried by the WantAgent. Do not render it in the
// notification body, where query parameters may be exposed to users.
normal: { title, text: content },
},
wantAgent: notificationWantAgent,
});
... ...