diff --git a/android/app/build.gradle b/android/app/build.gradle index a859aca81..300ccdeba 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -49,7 +49,7 @@ android { applicationId 'com.exptech.dpip' minSdkVersion 26 targetSdkVersion 36 - versionCode 300104018 + versionCode 300104019 versionName flutterVersionName multiDexEnabled true resConfigs "en", "ko", "zh-rTW", "ja", "zh-rCN" diff --git a/android/app/src/main/jniLibs/arm64-v8a/libeszstd-android64.so b/android/app/src/main/jniLibs/arm64-v8a/libeszstd-android64.so new file mode 100755 index 000000000..92dec0bf5 Binary files /dev/null and b/android/app/src/main/jniLibs/arm64-v8a/libeszstd-android64.so differ diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libeszstd-android32.so b/android/app/src/main/jniLibs/armeabi-v7a/libeszstd-android32.so new file mode 100644 index 000000000..30e2791ed Binary files /dev/null and b/android/app/src/main/jniLibs/armeabi-v7a/libeszstd-android32.so differ diff --git a/android/app/src/main/jniLibs/x86_64/libeszstd-android64.so b/android/app/src/main/jniLibs/x86_64/libeszstd-android64.so new file mode 100755 index 000000000..c93970d3d Binary files /dev/null and b/android/app/src/main/jniLibs/x86_64/libeszstd-android64.so differ diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c85d7b54c..88163109a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -78,9 +78,6 @@ PODS: - Flutter - fluttertoast (0.0.2): - Flutter - - gal (1.0.0): - - Flutter - - FlutterMacOS - geolocator_apple (1.2.0): - Flutter - FlutterMacOS @@ -165,8 +162,6 @@ PODS: - FlutterMacOS - url_launcher_ios (0.0.1): - Flutter - - zstandard_ios (0.0.1): - - Flutter DEPENDENCIES: - awesome_notifications (from `.symlinks/plugins/awesome_notifications/ios`) @@ -179,7 +174,6 @@ DEPENDENCIES: - flutter_compass (from `.symlinks/plugins/flutter_compass/ios`) - flutter_icmp_ping (from `.symlinks/plugins/flutter_icmp_ping/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - gal (from `.symlinks/plugins/gal/darwin`) - geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/darwin`) - maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`) @@ -190,7 +184,6 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - zstandard_ios (from `.symlinks/plugins/zstandard_ios/ios`) SPEC REPOS: trunk: @@ -233,8 +226,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_icmp_ping/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - gal: - :path: ".symlinks/plugins/gal/darwin" geolocator_apple: :path: ".symlinks/plugins/geolocator_apple/darwin" in_app_purchase_storekit: @@ -255,17 +246,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite_darwin/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - zstandard_ios: - :path: ".symlinks/plugins/zstandard_ios/ios" SPEC CHECKSUMS: - awesome_notifications: 0f432b28098d193920b11a44cfa9d2d9313a3888 - awesome_notifications_fcm: ad14f584c81e2488ae4310ab96331327dcbb5368 - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + awesome_notifications: dd5518ff1c80be03d4f1c40f04da9d9cc2a37af5 + awesome_notifications_fcm: 707931990883faf918db11438bdddded93b2b3a6 + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e - firebase_core: 995454a784ff288be5689b796deb9e9fa3601818 firebase_crashlytics: 30dcd6dfd2fe895c0848af46722a4227346c19aa - firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707 + firebase_core: 99a37263b3c27536063a7b601d9e2a49400a433c + firebase_messaging: bf6697c61f31c7cc0f654131212ff04c0115c2c7 FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e FirebaseCoreExtension: edbd30474b5ccf04e5f001470bdf6ea616af2435 FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 @@ -275,29 +264,27 @@ SPEC CHECKSUMS: FirebaseRemoteConfigInterop: 1c6135e8a094cc6368949f5faeeca7ee8948b8aa FirebaseSessions: b9a92c1c51bbb81e78fc3142cda6d925d700f8e7 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_compass: b236ab69b61545cce89fd58527f401a7587d5cc1 - flutter_icmp_ping: 47c1df3440c18ddd39fc457e607bb3b42d4a339f - fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - gal: baecd024ebfd13c441269ca7404792a7152fde89 - geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e + flutter_compass: cbbd285cea1584c7ac9c4e0c3e1f17cbea55e855 + flutter_icmp_ping: 2b159955eee0c487c766ad83fec224ae35e7c935 + fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f + geolocator_apple: 66b711889fd333205763b83c9dcf0a57a28c7afd GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 - in_app_purchase_storekit: 22cca7d08eebca9babdf4d07d0baccb73325d3c8 + in_app_purchase_storekit: 2342c0a5da86593124d08dd13d920f39a52b273a IosAwnCore: 653786a911089012092ce831f2945cd339855a89 IosAwnFcmCore: 1bdb9054b2e00187d00f1ffcfbb1855949a7b82f MapLibre: 7f24faba45439f80ccb0f83393c29fa32cb81952 - maplibre_gl: d83126f1b19adee5e1071c453b421efd5fc99883 + maplibre_gl: 5144d1208ed5ee49dfb2023670180f9af5517cce nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb - sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - zstandard_ios: 09cab18e0ee494020cc24d82a95c5bf4252d240c + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa PODFILE CHECKSUM: 3d88bce62bfe048ac33ca00d3fb1bc02caeda4d3 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 6afac8e9a..8253851d0 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,6 +2,7 @@ import CoreLocation import Flutter import UIKit import Intents +import Photos import UserNotifications @UIApplicationMain @@ -116,6 +117,31 @@ class AppDelegate: FlutterAppDelegate, CLLocationManagerDelegate { result(FlutterMethodNotImplemented) } } + + let imageSaverChannel = FlutterMethodChannel( + name: "image_saver", + binaryMessenger: controller.binaryMessenger + ) + + imageSaverChannel.setMethodCallHandler { call, result in + if call.method == "saveImage", + let args = call.arguments as? [String: Any], + let path = args["path"] as? String { + + if let image = UIImage(contentsOfFile: path) { + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) + result(nil) + } else { + result(FlutterError( + code: "INVALID_IMAGE", + message: "Cannot load image from path", + details: nil + )) + } + } else { + result(FlutterMethodNotImplemented) + } + } } private func setupLocationManager() { diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d19f8f31a..6cfb71665 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -53,8 +53,6 @@ DPIP 將利用你的位置資訊,用以自動設定所在地並在地震發生時能較準確地預估所在地的最大震度。 NSPhotoLibraryAddUsageDescription 用於儲存地震報告圖片。 - NSPhotoLibraryUsageDescription - 用於儲存地震報告圖片至您的相簿。 NSUserActivityTypes OpenMonitorIntentIntent diff --git a/lib/api/exptech.dart b/lib/api/exptech.dart index ccec023cc..9ba79e75c 100644 --- a/lib/api/exptech.dart +++ b/lib/api/exptech.dart @@ -25,15 +25,14 @@ import 'package:dpip/models/settings/notify.dart'; import 'package:dpip/utils/extensions/response.dart'; import 'package:dpip/utils/extensions/string.dart'; import 'package:dpip/utils/log.dart'; +import 'package:es_compression/zstd.dart'; import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:option_result/result.dart'; -import 'package:zstandard/zstandard.dart'; class _GzipClient extends http.BaseClient { final http.Client _inner; - final Zstandard _zstd = Zstandard(); _GzipClient(this._inner); @@ -49,14 +48,7 @@ class _GzipClient extends http.BaseClient { final compressedBody = await response.stream.toBytes(); try { - final decompressedBody = await _zstd.decompress(compressedBody); - - if (decompressedBody == null) { - throw HttpException( - 'Failed to decompress zstd response', - uri: request.url, - ); - } + final decompressedBody = zstd.decode(compressedBody); final headers = Map.from(response.headers); headers.remove('content-encoding'); diff --git a/lib/core/fcm.dart b/lib/core/fcm.dart index aaa20286e..ee96ddd1b 100644 --- a/lib/core/fcm.dart +++ b/lib/core/fcm.dart @@ -6,10 +6,10 @@ import 'package:dpip/core/preference.dart'; import 'package:dpip/main.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; Future fcmInit() async { await Firebase.initializeApp(); - if (Platform.isAndroid) { await AwesomeNotificationsFcm().initialize( onFcmTokenHandle: onTokenHandle, @@ -18,7 +18,7 @@ Future fcmInit() async { licenseKeys: [ '2024-08-26==N0LtVWu49ox9yV8eaDrh8rGyji/iKzLaB6anluLFIPESM/rUtf0OTUDyExMB+hp8YqnfA9UMcwvT5i5lTcsB73WKbh2+cYbYwtSZoxjuSUUbNxzhnlH2uiD7CNYvtniORC69TStgEfXnYZ1dEfWe5p6Nwi4wDS7vTfyTOH2NqCW+5293ypcu6+7se2PxLGOF1s8YKbM3HU8nYk8juChbFNoxX/Y0pHOH+MvXi070o1+3SPL98BS9bPQQ0e9a9MgYpxRqthP/mT1Yx2AX4+d+Qb6NNiz8ub+rl1HhZc7vmy5bntJSwcculDhXG3YOP3uXeYYyc2L+NKqkHPYpflblOg==', ], - debug: true, + debug: kDebugMode, ); await AwesomeNotificationsFcm().requestFirebaseAppToken(); } else if (Platform.isIOS) { diff --git a/lib/global.dart b/lib/global.dart index d9d237f40..0264f8957 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -5,11 +5,11 @@ import 'package:dpip/api/exptech.dart'; import 'package:dpip/api/model/location/location.dart'; import 'package:dpip/utils/extensions/asset_bundle.dart'; import 'package:dpip/utils/log.dart'; +import 'package:es_compression/zstd.dart'; import 'package:flutter/services.dart'; import 'package:geojson_vi/geojson_vi.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:zstandard/zstandard.dart'; typedef TimeTable = Map>; @@ -34,12 +34,7 @@ class Global { late List decompressed; if (assetPath.endsWith('.zst')) { - final zstd = Zstandard(); - final result = await zstd.decompress(bytes); - if (result == null) { - throw Exception('zstd decompress failed'); - } - decompressed = result; + decompressed = zstd.decode(bytes); } else if (assetPath.endsWith('.gz')) { decompressed = GZipCodec().decode(bytes); } else { diff --git a/lib/main.dart b/lib/main.dart index 46310e5c1..f755e2d01 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -107,7 +107,6 @@ void main() async { } final overallEndTime = DateTime.now(); - talker.log('--- 冷啟動偵測結束 ---'); talker.log( '🚨 總初始化耗時 (runApp 前): ${overallEndTime.difference(overallStartTime).inMilliseconds}ms', ); diff --git a/lib/route/image_viewer/image_viewer.dart b/lib/route/image_viewer/image_viewer.dart index 83db1f27e..cdd002ab1 100644 --- a/lib/route/image_viewer/image_viewer.dart +++ b/lib/route/image_viewer/image_viewer.dart @@ -6,6 +6,7 @@ import 'package:dpip/core/i18n.dart'; import 'package:dpip/utils/extensions/build_context.dart'; import 'package:dpip/utils/toast.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:gal/gal.dart'; import 'package:http/http.dart'; import 'package:material_symbols_icons/symbols.dart'; @@ -28,6 +29,16 @@ class ImageViewerRoute extends StatefulWidget { State createState() => _ImageViewerRouteState(); } +class ImageSaver { + static const _channel = MethodChannel('image_saver'); + + static Future saveToPhotos(String path) async { + await _channel.invokeMethod('saveImage', { + 'path': path, + }); + } +} + class _ImageViewerRouteState extends State { final TransformationController _controller = TransformationController(); bool isDownloading = false; @@ -92,26 +103,17 @@ class _ImageViewerRouteState extends State { final res = await get(Uri.parse(widget.imageUrl)); - // 保存图片到临时目录 final tempDir = await getTemporaryDirectory(); final tempFile = File('${tempDir.path}/${widget.imageName}'); await tempFile.writeAsBytes(res.bodyBytes); try { - // 保存到相册 if (Platform.isAndroid) { await Gal.putImage(tempFile.path, album: 'DPIP'); } else { - await Permission.photosAddOnly.request(); - try { - await Gal.putImage(tempFile.path); - } catch (_) { - final upgrade = await Permission.photos.request(); - if (upgrade.isGranted) { - await Gal.putImage(tempFile.path, album: 'DPIP'); - } - } + await ImageSaver.saveToPhotos(tempFile.path); } + if (!mounted) return; showToast( context, ToastWidget.text( @@ -120,7 +122,6 @@ class _ImageViewerRouteState extends State { ), ); } finally { - // 清理临时文件 if (await tempFile.exists()) { await tempFile.delete(); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 289dfac01..4b8ad0074 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -20,7 +20,6 @@ import share_plus import shared_preferences_foundation import sqflite_darwin import url_launcher_macos -import zstandard_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AwesomeNotificationsPlugin.register(with: registry.registrar(forPlugin: "AwesomeNotificationsPlugin")) @@ -38,5 +37,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - ZstandardMacosPlugin.register(with: registry.registrar(forPlugin: "ZstandardMacosPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index da65a6fc4..25476ab8a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -339,6 +339,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.7" + es_compression: + dependency: "direct main" + description: + name: es_compression + sha256: c1ff7af54802631cf5c3942cb67bb99daadcc087f573ca99a9de91002d1a7ece + url: "https://pub.dev" + source: hosted + version: "2.0.15" expressive_refresh: dependency: transitive description: @@ -1749,70 +1757,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.3" - zstandard: - dependency: "direct main" - description: - name: zstandard - sha256: d2cae71d34dfdd014525259be2f84ebc2a738c94cc4232515df44a1415dde9d3 - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_android: - dependency: transitive - description: - name: zstandard_android - sha256: "7d85d814f147faa7a33d5cd5b5610dea451a57f95b34f98c93723bb2f2efbf98" - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_ios: - dependency: transitive - description: - name: zstandard_ios - sha256: "50e6b3d05ad6ecf8def65c9fae46d4b786edd89150fd2bb207cdcef918946f0d" - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_linux: - dependency: transitive - description: - name: zstandard_linux - sha256: "66a61f7839016671a18448b73dab1910f7390bd9ab15bc118c8591381103f5b2" - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_macos: - dependency: transitive - description: - name: zstandard_macos - sha256: f4e02dc613f9d0c960edad8a330b41c4e0f0a548aea26668a12f45eebacd6aa1 - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_platform_interface: - dependency: transitive - description: - name: zstandard_platform_interface - sha256: "0261e2ce3520eace49e5d1e766e69a8c2613d24f0805c94e08544a139ad20e48" - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_web: - dependency: transitive - description: - name: zstandard_web - sha256: ed0d385dab253fefbe7c42e4eb7b58d3f2425713420402cc3f3b281d5a49e8a7 - url: "https://pub.dev" - source: hosted - version: "1.3.29" - zstandard_windows: - dependency: transitive - description: - name: zstandard_windows - sha256: "86bbd10b9d563dd22eedd4df94904078ac443d8f941b79a72c4fda534da76461" - url: "https://pub.dev" - source: hosted - version: "1.3.29" sdks: dart: ">=3.10.0 <4.0.0" flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 01212ea8a..b8396b69b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: url: https://github.com/ExpTechTW/Disable-Battery-Optimizations.git ref: 2addde3 dynamic_color: ^1.8.1 + es_compression: ^2.0.0 firebase_core: 3.15.2 #4.0.0 升級最低支援iOS 15 firebase_messaging: 15.2.10 flex_color_picker: ^3.8.0 @@ -75,7 +76,6 @@ dependencies: talker_flutter: ^5.1.9 timezone: ^0.10.1 url_launcher: ^6.3.2 - zstandard: ^1.3.29 dev_dependencies: build_runner: ^2.10.4