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