Showing
9 changed files
with
92 additions
and
21 deletions
| @@ -190,8 +190,7 @@ class CustomWatchThemePreviewController extends GetxController { | @@ -190,8 +190,7 @@ class CustomWatchThemePreviewController extends GetxController { | ||
| 190 | WatchThemeSyncDialog( | 190 | WatchThemeSyncDialog( |
| 191 | themeImageUrl: themeItem.energeticImage, | 191 | themeImageUrl: themeItem.energeticImage, |
| 192 | onSync: () async { | 192 | onSync: () async { |
| 193 | - if (device?.isPaired != true || | ||
| 194 | - device?.isWatchAppInstalled != true) { | 193 | + if (device?.isPaired != true || device?.isWatchAppInstalled != true) { |
| 195 | Get.back(); | 194 | Get.back(); |
| 196 | await Get.dialog<void>( | 195 | await Get.dialog<void>( |
| 197 | const WatchThemeNoWatchDialog(), | 196 | const WatchThemeNoWatchDialog(), |
| @@ -217,10 +216,20 @@ class CustomWatchThemePreviewController extends GetxController { | @@ -217,10 +216,20 @@ class CustomWatchThemePreviewController extends GetxController { | ||
| 217 | 216 | ||
| 218 | isApplying.value = true; | 217 | isApplying.value = true; |
| 219 | 218 | ||
| 219 | + WatchTransformTheme? downloadedTheme; | ||
| 220 | + if (themeItem.isDefaultTheme != 1) { | ||
| 221 | + try { | ||
| 222 | + downloadedTheme = await _syncPayloadBuilder.build(themeItem); | ||
| 223 | + } catch (e) { | ||
| 224 | + AppToast.show(l10n.watchThemeImageDownloadFailed(e.toString())); | ||
| 225 | + isApplying.value = false; | ||
| 226 | + return false; | ||
| 227 | + } | ||
| 228 | + } | ||
| 229 | + | ||
| 220 | var syncSuccess = true; | 230 | var syncSuccess = true; |
| 221 | try { | 231 | try { |
| 222 | - final payload = await _syncPayloadBuilder.build(themeItem); | ||
| 223 | - final success = await WearEngineHostApi().syncWatchTheme(payload); | 232 | + final success = await WearEngineHostApi().syncWatchTheme(downloadedTheme); |
| 224 | if (success) { | 233 | if (success) { |
| 225 | final result = await _themeApi.applyTheme(themeId); | 234 | final result = await _themeApi.applyTheme(themeId); |
| 226 | if (result is! AppSuccess<void>) { | 235 | if (result is! AppSuccess<void>) { |
| @@ -161,8 +161,7 @@ class WatchThemePreviewController extends GetxController { | @@ -161,8 +161,7 @@ class WatchThemePreviewController extends GetxController { | ||
| 161 | WatchThemeSyncDialog( | 161 | WatchThemeSyncDialog( |
| 162 | themeImageUrl: themeItem.energeticImage, | 162 | themeImageUrl: themeItem.energeticImage, |
| 163 | onSync: () async { | 163 | onSync: () async { |
| 164 | - if (device?.isPaired != true || | ||
| 165 | - device?.isWatchAppInstalled != true) { | 164 | + if (device?.isPaired != true || device?.isWatchAppInstalled != true) { |
| 166 | Get.back(); | 165 | Get.back(); |
| 167 | await Get.dialog<void>( | 166 | await Get.dialog<void>( |
| 168 | const WatchThemeNoWatchDialog(), | 167 | const WatchThemeNoWatchDialog(), |
| @@ -188,10 +187,20 @@ class WatchThemePreviewController extends GetxController { | @@ -188,10 +187,20 @@ class WatchThemePreviewController extends GetxController { | ||
| 188 | 187 | ||
| 189 | isApplying.value = true; | 188 | isApplying.value = true; |
| 190 | 189 | ||
| 190 | + WatchTransformTheme? downloadedTheme; | ||
| 191 | + if (themeItem.isDefaultTheme != 1) { | ||
| 192 | + try { | ||
| 193 | + downloadedTheme = await _syncPayloadBuilder.build(themeItem); | ||
| 194 | + } catch (e) { | ||
| 195 | + AppToast.show(l10n.watchThemeImageDownloadFailed(e.toString())); | ||
| 196 | + isApplying.value = false; | ||
| 197 | + return false; | ||
| 198 | + } | ||
| 199 | + } | ||
| 200 | + | ||
| 191 | var syncSuccess = true; | 201 | var syncSuccess = true; |
| 192 | try { | 202 | try { |
| 193 | - final payload = await _syncPayloadBuilder.build(themeItem); | ||
| 194 | - final success = await WearEngineHostApi().syncWatchTheme(payload); | 203 | + final success = await WearEngineHostApi().syncWatchTheme(downloadedTheme); |
| 195 | if (success) { | 204 | if (success) { |
| 196 | final result = await _themeApi.applyTheme(themeId); | 205 | final result = await _themeApi.applyTheme(themeId); |
| 197 | if (result is! AppSuccess<void>) { | 206 | if (result is! AppSuccess<void>) { |
| 1 | import 'dart:io'; | 1 | import 'dart:io'; |
| 2 | +import 'dart:typed_data'; | ||
| 2 | 3 | ||
| 3 | import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_theme_models.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_theme_models.dart'; |
| 4 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; | 5 | import 'package:doublefeel_flutter/pigeon/wear_engine_api.g.dart'; |
| 5 | -import 'package:flutter/foundation.dart'; | ||
| 6 | import 'package:flutter/services.dart'; | 6 | import 'package:flutter/services.dart'; |
| 7 | 7 | ||
| 8 | class WatchThemeSyncPayloadBuilder { | 8 | class WatchThemeSyncPayloadBuilder { |
| @@ -46,18 +46,37 @@ class WatchThemeSyncPayloadBuilder { | @@ -46,18 +46,37 @@ class WatchThemeSyncPayloadBuilder { | ||
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | Future<Uint8List?> _downloadImage(Uri uri) async { | 48 | Future<Uint8List?> _downloadImage(Uri uri) async { |
| 49 | - final client = HttpClient(); | ||
| 50 | - try { | ||
| 51 | - final request = await client.getUrl(uri); | ||
| 52 | - final response = await request.close(); | ||
| 53 | - if (response.statusCode < 200 || response.statusCode >= 300) { | ||
| 54 | - return null; | 49 | + const maxAttempts = 3; |
| 50 | + for (var attempt = 0; attempt < maxAttempts; attempt += 1) { | ||
| 51 | + final client = HttpClient() | ||
| 52 | + ..connectionTimeout = const Duration(seconds: 15) | ||
| 53 | + ..idleTimeout = const Duration(seconds: 5); | ||
| 54 | + try { | ||
| 55 | + final request = await client.getUrl(uri); | ||
| 56 | + request.headers.set(HttpHeaders.acceptHeader, 'image/*,*/*'); | ||
| 57 | + request.headers.set(HttpHeaders.connectionHeader, 'close'); | ||
| 58 | + request.headers.set( | ||
| 59 | + HttpHeaders.userAgentHeader, | ||
| 60 | + 'doublefeel_flutter/watch_theme', | ||
| 61 | + ); | ||
| 62 | + final response = await request.close(); | ||
| 63 | + if (response.statusCode < 200 || response.statusCode >= 300) { | ||
| 64 | + return null; | ||
| 65 | + } | ||
| 66 | + final builder = BytesBuilder(copy: false); | ||
| 67 | + await for (final chunk in response) { | ||
| 68 | + builder.add(chunk); | ||
| 69 | + } | ||
| 70 | + return builder.takeBytes(); | ||
| 71 | + } catch (_) { | ||
| 72 | + if (attempt == maxAttempts - 1) { | ||
| 73 | + return null; | ||
| 74 | + } | ||
| 75 | + await Future<void>.delayed(Duration(milliseconds: 150 * (attempt + 1))); | ||
| 76 | + } finally { | ||
| 77 | + client.close(force: true); | ||
| 55 | } | 78 | } |
| 56 | - return consolidateHttpClientResponseBytes(response); | ||
| 57 | - } catch (_) { | ||
| 58 | - return null; | ||
| 59 | - } finally { | ||
| 60 | - client.close(force: true); | ||
| 61 | } | 79 | } |
| 80 | + return null; | ||
| 62 | } | 81 | } |
| 63 | } | 82 | } |
| @@ -659,6 +659,15 @@ | @@ -659,6 +659,15 @@ | ||
| 659 | "watchThemeAbandonMessage": "Your changes won't be saved if you close this page. Discard them?", | 659 | "watchThemeAbandonMessage": "Your changes won't be saved if you close this page. Discard them?", |
| 660 | "watchThemeContinueEditing": "Continue Editing", | 660 | "watchThemeContinueEditing": "Continue Editing", |
| 661 | "watchThemeImageUploadFailed": "Image upload failed. Please try again", | 661 | "watchThemeImageUploadFailed": "Image upload failed. Please try again", |
| 662 | + "watchThemeImageDownloadFailed": "Failed to download theme images: {error}", | ||
| 663 | + "@watchThemeImageDownloadFailed": { | ||
| 664 | + "description": "Toast shown when watch theme images fail to download", | ||
| 665 | + "placeholders": { | ||
| 666 | + "error": { | ||
| 667 | + "type": "String" | ||
| 668 | + } | ||
| 669 | + } | ||
| 670 | + }, | ||
| 662 | "watchThemeCreateFailed": "Failed to create watch face. Please try again", | 671 | "watchThemeCreateFailed": "Failed to create watch face. Please try again", |
| 663 | "watchThemeDeleteTheme": "Delete Theme", | 672 | "watchThemeDeleteTheme": "Delete Theme", |
| 664 | "watchThemeDeleteMessage": "Deleted themes cannot be restored. Delete this theme?", | 673 | "watchThemeDeleteMessage": "Deleted themes cannot be restored. Delete this theme?", |
| @@ -1038,6 +1038,15 @@ | @@ -1038,6 +1038,15 @@ | ||
| 1038 | "watchThemeAbandonMessage": "关闭此页面后,已编辑的内容不会保留,是否放弃编辑?", | 1038 | "watchThemeAbandonMessage": "关闭此页面后,已编辑的内容不会保留,是否放弃编辑?", |
| 1039 | "watchThemeContinueEditing": "继续编辑", | 1039 | "watchThemeContinueEditing": "继续编辑", |
| 1040 | "watchThemeImageUploadFailed": "图片上传失败,请重试", | 1040 | "watchThemeImageUploadFailed": "图片上传失败,请重试", |
| 1041 | + "watchThemeImageDownloadFailed": "主题图片下载失败:{error}", | ||
| 1042 | + "@watchThemeImageDownloadFailed": { | ||
| 1043 | + "description": "表盘主题图片下载失败提示", | ||
| 1044 | + "placeholders": { | ||
| 1045 | + "error": { | ||
| 1046 | + "type": "String" | ||
| 1047 | + } | ||
| 1048 | + } | ||
| 1049 | + }, | ||
| 1041 | "watchThemeCreateFailed": "创建表盘失败,请重试", | 1050 | "watchThemeCreateFailed": "创建表盘失败,请重试", |
| 1042 | "watchThemeDeleteTheme": "删除主题", | 1051 | "watchThemeDeleteTheme": "删除主题", |
| 1043 | "watchThemeDeleteMessage": "主题删除后无法恢复,确认删除该主题吗?", | 1052 | "watchThemeDeleteMessage": "主题删除后无法恢复,确认删除该主题吗?", |
| @@ -3867,6 +3867,12 @@ abstract class AppLocalizations { | @@ -3867,6 +3867,12 @@ abstract class AppLocalizations { | ||
| 3867 | /// **'图片上传失败,请重试'** | 3867 | /// **'图片上传失败,请重试'** |
| 3868 | String get watchThemeImageUploadFailed; | 3868 | String get watchThemeImageUploadFailed; |
| 3869 | 3869 | ||
| 3870 | + /// 表盘主题图片下载失败提示 | ||
| 3871 | + /// | ||
| 3872 | + /// In zh, this message translates to: | ||
| 3873 | + /// **'主题图片下载失败:{error}'** | ||
| 3874 | + String watchThemeImageDownloadFailed(String error); | ||
| 3875 | + | ||
| 3870 | /// No description provided for @watchThemeCreateFailed. | 3876 | /// No description provided for @watchThemeCreateFailed. |
| 3871 | /// | 3877 | /// |
| 3872 | /// In zh, this message translates to: | 3878 | /// In zh, this message translates to: |
| @@ -2169,6 +2169,11 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -2169,6 +2169,11 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 2169 | 'Image upload failed. Please try again'; | 2169 | 'Image upload failed. Please try again'; |
| 2170 | 2170 | ||
| 2171 | @override | 2171 | @override |
| 2172 | + String watchThemeImageDownloadFailed(String error) { | ||
| 2173 | + return 'Failed to download theme images: $error'; | ||
| 2174 | + } | ||
| 2175 | + | ||
| 2176 | + @override | ||
| 2172 | String get watchThemeCreateFailed => | 2177 | String get watchThemeCreateFailed => |
| 2173 | 'Failed to create watch face. Please try again'; | 2178 | 'Failed to create watch face. Please try again'; |
| 2174 | 2179 |
| @@ -2061,6 +2061,11 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -2061,6 +2061,11 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 2061 | String get watchThemeImageUploadFailed => '图片上传失败,请重试'; | 2061 | String get watchThemeImageUploadFailed => '图片上传失败,请重试'; |
| 2062 | 2062 | ||
| 2063 | @override | 2063 | @override |
| 2064 | + String watchThemeImageDownloadFailed(String error) { | ||
| 2065 | + return '主题图片下载失败:$error'; | ||
| 2066 | + } | ||
| 2067 | + | ||
| 2068 | + @override | ||
| 2064 | String get watchThemeCreateFailed => '创建表盘失败,请重试'; | 2069 | String get watchThemeCreateFailed => '创建表盘失败,请重试'; |
| 2065 | 2070 | ||
| 2066 | @override | 2071 | @override |
-
Please register or login to post a comment