From 344af390d5b80f9b9a14cd107588c9d8258ad31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=88=E7=A5=A5?= Date: Mon, 18 May 2026 13:32:51 +0800 Subject: [PATCH] init --- .gitignore | 45 + .metadata | 33 + LICENSE | 201 +++ README.md | 104 ++ analysis_options.yaml | 28 + android/.gitignore | 14 + android/app/build.gradle.kts | 49 + android/app/src/debug/AndroidManifest.xml | 7 + android/app/src/main/AndroidManifest.xml | 68 + .../com/example/luban_imager/MainActivity.kt | 515 ++++++++ .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 5680 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 3930 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6917 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 9372 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 11495 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values-zh/strings.xml | 5 + android/app/src/main/res/values/strings.xml | 5 + android/app/src/main/res/values/styles.xml | 18 + android/app/src/main/res/xml/file_paths.xml | 6 + android/app/src/profile/AndroidManifest.xml | 7 + android/build.gradle.kts | 24 + android/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle.kts | 26 + assets/icon/luban_imager_icon.svg | 10 + assets/icon/luban_imager_icon_1024.png | Bin 0 -> 27723 bytes ios/.gitignore | 34 + ios/Flutter/AppFrameworkInfo.plist | 24 + ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 638 +++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + ios/Runner/AppDelegate.swift | 16 + .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 27723 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 1637 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 3212 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 4745 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 2308 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 4572 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 6447 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 3212 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 6269 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 8360 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 8360 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 10625 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 5866 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 9431 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 10373 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + ios/Runner/Base.lproj/LaunchScreen.storyboard | 37 + ios/Runner/Base.lproj/Main.storyboard | 26 + ios/Runner/Info.plist | 89 ++ ios/Runner/Runner-Bridging-Header.h | 1 + ios/Runner/SceneDelegate.swift | 644 +++++++++ ios/Runner/en.lproj/InfoPlist.strings | 2 + ios/Runner/zh-Hans.lproj/InfoPlist.strings | 2 + ios/Runner/zh-Hant.lproj/InfoPlist.strings | 2 + ios/RunnerTests/RunnerTests.swift | 12 + lib/main.dart | 1170 +++++++++++++++++ pubspec.lock | 213 +++ pubspec.yaml | 89 ++ test/widget_test.dart | 26 + 76 files changed, 4538 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/kotlin/com/example/luban_imager/MainActivity.kt create mode 100644 android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 android/app/src/main/res/drawable/launch_background.xml create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values-night/styles.xml create mode 100644 android/app/src/main/res/values-zh/strings.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/main/res/xml/file_paths.xml create mode 100644 android/app/src/profile/AndroidManifest.xml create mode 100644 android/build.gradle.kts create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle.kts create mode 100644 assets/icon/luban_imager_icon.svg create mode 100644 assets/icon/luban_imager_icon_1024.png create mode 100644 ios/.gitignore create mode 100644 ios/Flutter/AppFrameworkInfo.plist create mode 100644 ios/Flutter/Debug.xcconfig create mode 100644 ios/Flutter/Release.xcconfig create mode 100644 ios/Runner.xcodeproj/project.pbxproj create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 ios/Runner/AppDelegate.swift create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 ios/Runner/Base.lproj/Main.storyboard create mode 100644 ios/Runner/Info.plist create mode 100644 ios/Runner/Runner-Bridging-Header.h create mode 100644 ios/Runner/SceneDelegate.swift create mode 100644 ios/Runner/en.lproj/InfoPlist.strings create mode 100644 ios/Runner/zh-Hans.lproj/InfoPlist.strings create mode 100644 ios/Runner/zh-Hant.lproj/InfoPlist.strings create mode 100644 ios/RunnerTests/RunnerTests.swift create mode 100644 lib/main.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/widget_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..6738224 --- /dev/null +++ b/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "00b0c91f06209d9e4a41f71b7a512d6eb3b9c694" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + - platform: android + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + - platform: ios + create_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + base_revision: 00b0c91f06209d9e4a41f71b7a512d6eb3b9c694 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0aa2653 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 The Luban Imager Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..acd36bb --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Luban Imager / 鲁班压图 + +Luban Imager 是一个基于 Flutter 的移动端图片压缩 App,目标是把优秀的 Luban 图片压缩能力包装成一个简单、直接、适合日常使用的小工具。 + +## 致谢 + +首先感谢上游 [Curzibn/Luban](https://github.com/Curzibn/Luban) 项目。Luban 提供了接近微信朋友圈压缩策略的 Android 图片压缩能力,本项目的 Android 端基于 `top.zibin:luban:2.0.1` 工作,并在此基础上补充了 Flutter 界面、系统图片选择/分享、压缩结果预览、覆盖原图等 App 侧能力。 + +本项目不是 Luban 官方 App。如果你需要压缩算法本身、Android 库接入方式或上游实现细节,请优先参考 Luban 原仓库。 + +## 主要功能 + +- 智能压缩:选择图片后自动执行 Luban 风格压缩,不需要手动填写质量、尺寸等参数。 +- 多入口导入:支持从 App 内选择图片,也支持从系统分享菜单接收图片。 +- 压缩结果预览:展示原图、压缩图、前后对比三种视图,并显示尺寸、文件大小和节省比例。 +- 安全回退:如果压缩结果比原图更大,会自动使用原图,避免负优化。 +- 分享导出:压缩完成后可通过系统分享面板导出压缩图片。 +- 原位覆盖:对于系统授权可写入的图片来源,支持覆盖原图;覆盖前会进行两次确认。 + +## 平台支持 + +| 平台 | 状态 | 说明 | +| --- | --- | --- | +| Android | 已支持 | 使用上游 Luban Android 库执行压缩,并已接入选择、分享、覆盖等能力。 | +| iOS | 理论支持,未实机测试 | iOS 端包含基于 ImageIO/UIKit 的 Luban 风格压缩实现,但作者目前没有 iPhone 设备,因此还没有经过真实设备验证。 | + +## 技术实现 + +- UI 使用 Flutter / Material 3。 +- Android 通过 `MethodChannel` 调用原生 Kotlin 代码,并使用 `top.zibin:luban:2.0.1` 压缩图片。 +- iOS 通过 `MethodChannel` 调用 Swift 代码,使用 ImageIO/UIKit 读取、缩放和编码图片。 +- 图片选择、分享和覆盖均尽量走系统能力,压缩过程在本地完成。 + +## 开发环境 + +建议使用 Flutter stable 版本。当前项目由 Flutter 模板生成,`pubspec.yaml` 中 Dart SDK 约束为: + +```sh +sdk: ^3.11.5 +``` + +准备依赖: + +```sh +flutter pub get +``` + +运行测试: + +```sh +flutter test +``` + +本地运行: + +```sh +flutter run +``` + +## 编译 + +### Android + +调试安装包: + +```sh +flutter build apk --debug +``` + +发布安装包: + +```sh +flutter build apk --release +``` + +按 CPU 架构拆分发布安装包: + +```sh +flutter build apk --release --split-per-abi +``` + +发布 AAB: + +```sh +flutter build appbundle --release +``` + +发布前建议修改 `android/app/build.gradle.kts` 中的 `applicationId`,并配置自己的 release 签名。当前工程为了方便本地运行,release 构建仍使用 debug signing config。 + +### iOS + +iOS 构建需要 macOS、Xcode 和有效签名配置: + +```sh +flutter build ios --release +``` + +注意:iOS 端目前只是理论支持,代码路径已经实现,但尚未在真实 iPhone 设备上验证。 + +## License + +本项目采用 Apache-2.0 License,见 [LICENSE](./LICENSE)。 + +上游 Luban 项目同样采用 Apache-2.0 License。Android 端依赖 Luban 时,请保留并遵守上游项目许可证要求。 diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..7a37a76 --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.luban_imager" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.luban_imager" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} + +dependencies { + implementation("androidx.core:core:1.13.1") + implementation("top.zibin:luban:2.0.1") +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..064306f --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/luban_imager/MainActivity.kt b/android/app/src/main/kotlin/com/example/luban_imager/MainActivity.kt new file mode 100644 index 0000000..7d9c354 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/luban_imager/MainActivity.kt @@ -0,0 +1,515 @@ +package com.example.luban_imager + +import android.app.Activity +import android.content.ClipData +import android.content.ContentValues +import android.content.Intent +import android.graphics.BitmapFactory +import android.media.MediaScannerConnection +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.OpenableColumns +import android.provider.MediaStore +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel +import java.io.File +import java.io.FileOutputStream +import java.util.UUID +import top.zibin.luban.api.Luban +import top.zibin.luban.api.OnCompressListener + +class MainActivity : FlutterActivity() { + private val channelName = "luban_imager/native_images" + private val pickImagesRequest = 4101 + + private var channel: MethodChannel? = null + private var pendingPickResult: MethodChannel.Result? = null + private val pendingSharedImages = mutableListOf>() + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName) + channel?.setMethodCallHandler { call, result -> + when (call.method) { + "pickImages" -> pickImages(result) + "takeSharedImages" -> takeSharedImages(result) + "compressImage" -> compressImage(call.arguments, result) + "overwriteOriginal" -> overwriteOriginal(call.arguments, result) + "saveToGallery" -> saveToGallery(call.arguments, result) + "shareImage" -> shareImage(call.arguments, result) + else -> result.notImplemented() + } + } + consumeSharedIntent(intent, notifyFlutter = false) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + consumeSharedIntent(intent, notifyFlutter = true) + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + pickImagesRequest -> finishPickImages(resultCode, data) + } + } + + private fun pickImages(result: MethodChannel.Result) { + if (pendingPickResult != null) { + result.error("busy", "正在选择图片", null) + return + } + + pendingPickResult = result + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "image/*" + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + } + + try { + startActivityForResult(intent, pickImagesRequest) + } catch (error: Exception) { + pendingPickResult = null + result.error("picker_unavailable", error.message, null) + } + } + + private fun finishPickImages(resultCode: Int, data: Intent?) { + val result = pendingPickResult ?: return + pendingPickResult = null + + if (resultCode != Activity.RESULT_OK || data == null) { + result.success(emptyList>()) + return + } + + val flags = data.flags and + (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + val payload = mutableListOf>() + + try { + val uri = extractUri(data) + if (uri != null) { + try { + if (flags != 0) { + contentResolver.takePersistableUriPermission(uri, flags) + } + } catch (_: SecurityException) { + // Some providers grant temporary access only. + } + payload.add(buildPickedImage(uri)) + } + result.success(payload) + } catch (error: Exception) { + result.error("pick_failed", error.message, null) + } + } + + private fun extractUri(data: Intent): Uri? { + return data.data ?: data.clipData?.getItemAt(0)?.uri + } + + private fun takeSharedImages(result: MethodChannel.Result) { + val images = pendingSharedImages.toList() + pendingSharedImages.clear() + result.success(images) + } + + private fun consumeSharedIntent(intent: Intent?, notifyFlutter: Boolean) { + val uris = extractSharedUris(intent) + if (uris.isEmpty()) { + return + } + + val payload = uris.mapNotNull { uri -> + try { + buildSharedImage(uri) + } catch (_: Exception) { + null + } + } + if (payload.isEmpty()) { + return + } + + pendingSharedImages.clear() + pendingSharedImages.addAll(payload) + if (notifyFlutter) { + channel?.invokeMethod("sharedImages", payload) + } + } + + @Suppress("DEPRECATION") + private fun extractSharedUris(intent: Intent?): List { + if (intent == null || intent.type?.startsWith("image/") != true) { + return emptyList() + } + + return when (intent.action) { + Intent.ACTION_SEND -> { + val stream = intent.getParcelableExtra(Intent.EXTRA_STREAM) + listOfNotNull(stream ?: intent.data) + } + Intent.ACTION_SEND_MULTIPLE -> { + intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.toList() + ?: emptyList() + } + else -> emptyList() + } + } + + private fun buildSharedImage(uri: Uri): Map { + val displayName = queryDisplayName(uri) ?: "image" + val previewFile = copyUriToCache(uri, displayName, "shared-originals") + val dimensions = readDimensionsFromFile(previewFile) + val originalSize = previewFile.length() + + return mapOf( + "sourceHandle" to Uri.fromFile(previewFile).toString(), + "displayName" to displayName, + "previewPath" to previewFile.absolutePath, + "originalSize" to originalSize, + "width" to dimensions.first, + "height" to dimensions.second, + "canOverwrite" to false, + ) + } + + private fun buildPickedImage(uri: Uri): Map { + val displayName = queryDisplayName(uri) ?: "image" + val previewFile = copyUriToCache(uri, displayName, "originals") + val dimensions = readDimensionsFromUri(uri).takeIf { it.first > 0 && it.second > 0 } + ?: readDimensionsFromFile(previewFile) + val originalSize = querySize(uri).takeIf { it > 0 } ?: previewFile.length() + + return mapOf( + "sourceHandle" to uri.toString(), + "displayName" to displayName, + "previewPath" to previewFile.absolutePath, + "originalSize" to originalSize, + "width" to dimensions.first, + "height" to dimensions.second, + "canOverwrite" to canOverwrite(uri), + ) + } + + private fun compressImage(arguments: Any?, result: MethodChannel.Result) { + val args = arguments as? Map<*, *> + val source = args?.get("sourceHandle") as? String + val previewPath = args?.get("previewPath") as? String + val originalSize = (args?.get("originalSize") as? Number)?.toLong() ?: -1L + val originalWidth = (args?.get("originalWidth") as? Number)?.toInt() ?: 0 + val originalHeight = (args?.get("originalHeight") as? Number)?.toInt() ?: 0 + if (source.isNullOrBlank()) { + result.error("bad_args", "缺少 sourceHandle", null) + return + } + + val outputDir = File(cacheDir, "compressed").apply { mkdirs() } + val uri = Uri.parse(source) + + try { + Luban.with(this) + .load(uri) + .setTargetDir(outputDir) + .setCompressListener(object : OnCompressListener { + override fun onStart() = Unit + + override fun onSuccess(file: File) { + val previewFile = previewPath?.let { File(it) } + val shouldUseOriginal = originalSize > 0 && + file.length() >= originalSize && + previewFile?.exists() == true + val outputFile = if (shouldUseOriginal) previewFile!! else file + val dimensions = if (shouldUseOriginal && + originalWidth > 0 && + originalHeight > 0 + ) { + Pair(originalWidth, originalHeight) + } else { + readDimensionsFromFile(outputFile) + } + val outputSize = if (shouldUseOriginal && originalSize > 0) { + originalSize + } else { + outputFile.length() + } + runOnUiThread { + result.success( + mapOf( + "path" to outputFile.absolutePath, + "outputSize" to outputSize, + "width" to dimensions.first, + "height" to dimensions.second, + "passthrough" to shouldUseOriginal, + ), + ) + } + } + + override fun onError(e: Throwable) { + runOnUiThread { + result.error("compress_failed", e.message, null) + } + } + }) + .launch() + } catch (error: Exception) { + result.error("compress_failed", error.message, null) + } + } + + private fun overwriteOriginal(arguments: Any?, result: MethodChannel.Result) { + val args = arguments as? Map<*, *> + val source = args?.get("sourceHandle") as? String + val compressedPath = args?.get("compressedPath") as? String + if (source.isNullOrBlank() || compressedPath.isNullOrBlank()) { + result.error("bad_args", "缺少覆盖参数", null) + return + } + + try { + copyFileToUri(File(compressedPath), Uri.parse(source)) + result.success(mapOf("overwritten" to true, "target" to source)) + } catch (error: Exception) { + result.error("overwrite_failed", error.message, null) + } + } + + private fun shareImage(arguments: Any?, result: MethodChannel.Result) { + val args = arguments as? Map<*, *> + val compressedPath = args?.get("compressedPath") as? String + val suggestedName = args?.get("suggestedName") as? String ?: "luban-image.jpg" + if (compressedPath.isNullOrBlank()) { + result.error("bad_args", "缺少 compressedPath", null) + return + } + + try { + val shareFile = makeShareCopy(File(compressedPath), suggestedName) + val shareUri = FileProvider.getUriForFile( + this, + "$packageName.fileprovider", + shareFile, + ) + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = mimeTypeForName(shareFile.name) + putExtra(Intent.EXTRA_STREAM, shareUri) + clipData = ClipData.newUri(contentResolver, shareFile.name, shareUri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + startActivity(Intent.createChooser(shareIntent, getString(R.string.share_image_title))) + result.success(mapOf("shared" to true)) + } catch (error: Exception) { + result.error("share_failed", error.message, null) + } + } + + private fun saveToGallery(arguments: Any?, result: MethodChannel.Result) { + val args = arguments as? Map<*, *> + val compressedPath = args?.get("compressedPath") as? String + val suggestedName = args?.get("suggestedName") as? String ?: "luban-image.jpg" + if (compressedPath.isNullOrBlank()) { + result.error("bad_args", "缺少 compressedPath", null) + return + } + + try { + val savedUri = writeImageToGallery(File(compressedPath), suggestedName) + result.success(mapOf("savedTo" to savedUri.toString())) + } catch (error: Exception) { + result.error("save_failed", error.message, null) + } + } + + private fun copyUriToCache(uri: Uri, displayName: String, child: String): File { + val dir = File(cacheDir, child).apply { mkdirs() } + val safeName = sanitizeFileName(displayName) + val target = File(dir, "${UUID.randomUUID()}-$safeName") + contentResolver.openInputStream(uri).use { input -> + requireNotNull(input) { "无法读取图片" } + FileOutputStream(target).use { output -> input.copyTo(output) } + } + return target + } + + private fun writeImageToGallery(source: File, suggestedName: String): Uri { + require(source.exists()) { "压缩文件不存在" } + val safeName = sanitizeFileName(suggestedName) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + writeImageToMediaStore(source, safeName) + } else { + writeImageToPublicPictures(source, safeName) + } + } + + private fun writeImageToMediaStore(source: File, safeName: String): Uri { + val values = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, safeName) + put(MediaStore.Images.Media.MIME_TYPE, mimeTypeForName(safeName)) + put( + MediaStore.Images.Media.RELATIVE_PATH, + "${Environment.DIRECTORY_PICTURES}/Luban Imager", + ) + put(MediaStore.Images.Media.IS_PENDING, 1) + } + val collection = MediaStore.Images.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY, + ) + val uri = requireNotNull(contentResolver.insert(collection, values)) { + "无法创建相册图片" + } + + try { + copyFileToUri(source, uri) + values.clear() + values.put(MediaStore.Images.Media.IS_PENDING, 0) + contentResolver.update(uri, values, null, null) + return uri + } catch (error: Exception) { + contentResolver.delete(uri, null, null) + throw error + } + } + + @Suppress("DEPRECATION") + private fun writeImageToPublicPictures(source: File, safeName: String): Uri { + val dir = File( + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), + "Luban Imager", + ).apply { mkdirs() } + val target = uniqueFile(dir, safeName) + source.inputStream().use { input -> + FileOutputStream(target).use { output -> input.copyTo(output) } + } + MediaScannerConnection.scanFile( + this, + arrayOf(target.absolutePath), + arrayOf(mimeTypeForName(target.name)), + null, + ) + return Uri.fromFile(target) + } + + private fun uniqueFile(dir: File, name: String): File { + val dot = name.lastIndexOf('.') + val baseName = if (dot > 0) name.substring(0, dot) else name + val extension = if (dot > 0) name.substring(dot) else "" + var index = 0 + var candidate = File(dir, name) + + while (candidate.exists()) { + index += 1 + candidate = File(dir, "${baseName}_$index$extension") + } + return candidate + } + + private fun makeShareCopy(source: File, suggestedName: String): File { + require(source.exists()) { "压缩文件不存在" } + val dir = File(cacheDir, "shared").apply { mkdirs() } + val target = File(dir, sanitizeFileName(suggestedName)) + if (target.exists()) { + target.delete() + } + source.inputStream().use { input -> + FileOutputStream(target).use { output -> input.copyTo(output) } + } + return target + } + + private fun copyFileToUri(source: File, targetUri: Uri) { + require(source.exists()) { "压缩文件不存在" } + source.inputStream().use { input -> + if (targetUri.scheme == "file") { + val path = requireNotNull(targetUri.path) { "目标路径无效" } + FileOutputStream(File(path)).use { output -> input.copyTo(output) } + } else { + contentResolver.openOutputStream(targetUri, "wt").use { output -> + requireNotNull(output) { "无法写入目标文件" } + input.copyTo(output) + } + } + } + } + + private fun queryDisplayName(uri: Uri): String? { + contentResolver.query(uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null) + ?.use { cursor -> + if (cursor.moveToFirst()) { + val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (index >= 0) { + return cursor.getString(index) + } + } + } + return uri.lastPathSegment + } + + private fun querySize(uri: Uri): Long { + contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null) + ?.use { cursor -> + if (cursor.moveToFirst()) { + val index = cursor.getColumnIndex(OpenableColumns.SIZE) + if (index >= 0) { + return cursor.getLong(index) + } + } + } + return -1L + } + + private fun readDimensionsFromUri(uri: Uri): Pair { + return try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + contentResolver.openInputStream(uri).use { input -> + BitmapFactory.decodeStream(input, null, options) + } + Pair(options.outWidth.coerceAtLeast(0), options.outHeight.coerceAtLeast(0)) + } catch (_: Exception) { + Pair(0, 0) + } + } + + private fun readDimensionsFromFile(file: File): Pair { + return try { + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeFile(file.absolutePath, options) + Pair(options.outWidth.coerceAtLeast(0), options.outHeight.coerceAtLeast(0)) + } catch (_: Exception) { + Pair(0, 0) + } + } + + private fun canOverwrite(uri: Uri): Boolean { + return try { + if (uri.scheme == "file") { + val path = uri.path ?: return false + return File(path).canWrite() + } + contentResolver.openFileDescriptor(uri, "rw")?.use { true } ?: false + } catch (_: Exception) { + false + } + } + + private fun sanitizeFileName(name: String): String { + val safe = name.replace(Regex("[^A-Za-z0-9._-]"), "_") + return safe.ifBlank { "image.jpg" } + } + + private fun mimeTypeForName(name: String): String { + val extension = name.substringAfterLast('.', "").lowercase() + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "image/jpeg" + } +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5b07e8927cfdc4d097e01d9e7339c632ec0e3971 GIT binary patch literal 5680 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 P2>{wi1NCY(+sOX`7;rZ{ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..f87fe322c22f86454dafda1e3004d9ed09d6e046 GIT binary patch literal 6917 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 T&(HrtB7nTKid2n+X~_Qo1DN9+ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ced1385c346008063a78f55e25a230af7f9ee072 GIT binary patch literal 9372 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1627258e18161d8166618939b29321c634a21273 GIT binary patch literal 11495 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values-zh/strings.xml b/android/app/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..e46869a --- /dev/null +++ b/android/app/src/main/res/values-zh/strings.xml @@ -0,0 +1,5 @@ + + + 鲁班压图 + 分享图片 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..f441e30 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Luban Imager + Share image + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/xml/file_paths.xml b/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..da52945 --- /dev/null +++ b/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,6 @@ + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..fbee1d8 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e4ef43f --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..ca7fe06 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.11.1" apply false + id("org.jetbrains.kotlin.android") version "2.2.20" apply false +} + +include(":app") diff --git a/assets/icon/luban_imager_icon.svg b/assets/icon/luban_imager_icon.svg new file mode 100644 index 0000000..778a7e1 --- /dev/null +++ b/assets/icon/luban_imager_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icon/luban_imager_icon_1024.png b/assets/icon/luban_imager_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..5e539061402443556cf019be2879485f2a5c1594 GIT binary patch literal 27723 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 Uq#C<(Oj>-GgYE}tPJ!qD09}l%g8%>k literal 0 HcmV?d00001 diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..391a902 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..2917eca --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,638 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5E7A01B32ECBC56A00123456 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5E7A01B72ECBC56A00123456 /* InfoPlist.strings */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5E7A01B42ECBC56A00123456 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 5E7A01B52ECBC56A00123456 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/InfoPlist.strings"; sourceTree = ""; }; + 5E7A01B62ECBC56A00123456 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 5E7A01B72ECBC56A00123456 /* InfoPlist.strings */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + "zh-Hans", + "zh-Hant", + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 5E7A01B32ECBC56A00123456 /* InfoPlist.strings in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + 7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 5E7A01B72ECBC56A00123456 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 5E7A01B42ECBC56A00123456 /* en */, + 5E7A01B52ECBC56A00123456 /* zh-Hans */, + 5E7A01B62ECBC56A00123456 /* zh-Hant */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.lubanImager; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..c30b367 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,16 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..0e5d040dbff9d5146dd43444fb73a149b7cdb4f3 GIT binary patch literal 27723 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 T+Hspl6vg-4kZB(K+ zw`N`3mXmVnViha8pe%AZQ4;Uo_mB6S_m7@)p67ec=Q*GA{P$#$Tpc!T+`16}fK84B zoQE_K|Fn{l

Ft6x&Q^yJ9Zd z1ap;K@{ZS)1m-AG-?LHAHbXGVrAEl~D~Ej(owSI0l*PB3)O zLYN}k9ogIj=s>mDUnj``D?B-oab{C_WCwU~Ex8lkV%dSjzJt%=3oB>$+WD%d`&^|8 z(G>e);fh&N4{{*K^rBXGNLN^yIV1D(34@7Lx#h-4FesZy<%ZV^Je3RO!*Qk_ze2)z z@ez}OQ5+PE6Dm@$E<>c{vtHzW+;+Vvbd?`Bj0-tO_a4c#dBwIRr5>o#)tJ-tl}S(Q z_F`J!Ma?U=ye+)h{79my5q(DhkC@9`e1r+6P~xCq+#KVM>cGkHmM?8CW_@;}@dt2jE=$W*Vlvk^mz;@HP6w-)OO+HLTb>odYAaS{PocHNhmYbEZ3fQa7^?}lI zzI04HnH7{%a~>@{z1LJrkok}n0wzCGzptt_^x^Cg=0s`5CFkD$rgm7ckfuqw;yz2_ zm72$5$v<(mw{_d|S{Ch`bj}BK&hjiI6&3Z8-UMUqlZtMO*RZ;1E*=2>(@RPtO+f3k0E=}$%_gHdKIP#d1UQO7rFF1D&Ux8(04 zv1t1mcKO-F?O_gK{;jtaCww~W)P+_-f^Hp&?p{Q_?Qf^OXTt~jfl54;g_6Hm9 zebTD&o?Ii2=o8fm%5E&`4wyS(zGC3zoWpVRXY zOCTrI<@9W3?s4C7+3px+-PG4IZm!m4OXS3+KLVo*gXm2y`|?f zL_h8hPC-L=;QSlw(W27F{hR{*hr2X%hdLR>qNj3@6~1ifmA%R@zvZtq4hL5pBewn| zCJAVacxktch|m*fIjp% z5?x)=NZJww@{UnaGyvte!K0Y^O~);_Lya>69&(T@_zR^-xE^@L!Fd~>n%E9A|F3*(JFpIhDX38uRNJ=|oEAbg8SAZCwQnTN2Bz^EKU z^;Byw&J9FT*qi6K-zUfFoipeFFCWRqv-b~r`Q^;eV~rLbjfY%WpMfl>=@iCFMw5GL Qq)!-d#Jl1e4pGwn23g18{{R30 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..84ae4cc03bac7f27fd67f7c3115714a1d507c631 GIT binary patch literal 3212 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

^gNIJ7In`c)@eR?)<1JA9F|bF;FO z`dIx)Tpual9}Z`mz4i~{%FmAry~Pr%Ls$UsYC=I>44()LF+PpbdpmVb-LZMH-!h!f z(9BLaWh?>&|8}>aKRN&520`0PenL+5_!`R@*C%he>o40H_ALc%k+pV2uX?J-I+Psw zjyIpA;Sg8avQ3+KZ7$OlPkOirhFBM@5pCjo3l`1IoA!OHV}BYvlSGk+=-Jga);P&X zLqSUHA_?kEru9GgA`eWC;rGIS>8d9?jT^H0!5Mu%FymuWo&~M1Z4GKt;q%@JFEjMR zQv3E_L6D%k(5%(XcaCSvy4gSAWw~4aHu&b8KMR{{{ocC-ZZG>g9;Vpx^lGrpaXJE5 z`S@p%UEnE=WZu3-+TD*skbL=?+uZlR4%J6Y*rSFBTHd!YnzFRMJkrW~F>!ksf;1ox z&Y{4PN%{>I-oV!JEDGdTR z3ixFK1$Uq0HdQcLSM)H7b8xV(z~2fjJ-LVU!Spgk%;U!=zN}`Gf%kQT^v3W0NTjI0!Ah*CY@}DQUN?;XsNv|sU}!>-x)%qa3`gNep}g8llpKxp(Mw|Q4Dc>m3LNc zQNf)l;NvLiE}GQca>877P0orqV~59N&9L56d4$8;t+<}?yVbZJctu?1oB1@n@v7cG z6t31d%9i2D>Or~O9rC=Kd_<%tDA8)d36VMP9(9}G94X*$*W5D9lKQ35B0^GWAwr>$ zm6%4gg^_ZN5i57q&+qrg@0{Ns-{+j?d7txs&g;CNKOZXU#4(v|YTEz+ka2dhM{gswaJGyp`y0AL0S02T!R0FEep?s0f)0SaDjzh3{LI4%xwn9DIh1vp5@x+Y$f)Lzo%CkVbHG z49wI53A2Ek?T5iEV6cZQ<+}d^h~dG6b8-I+4Bci*Z2|lKmp~>EaZ%(m#E5@pu3XLK zZQVKUY=0Ev(=kzyfWl0vvWiynMPNg*o9O=ZbLYFK(09{!nLwX^wlKnnUU8m8*6z_6MA_z7w0(F=bYGAS`M3R zytn)kv%F%p!HybeyXhcddr{_YudD;& zv!W7tLfo>xe;7N&b>gHzYp8e9NkJg(Dgvm_f{X(+k4Q_HUmK*H)((hNz4sOjDue1> z|JmMa{Rt>+yC4?gR$VuHwkp0)l_+0AuH(a*QmTq`WfAdq1Gl>;8Jw%PIQTCJH~1l& zWfPFfcz2q>f@?v*8}2Me+5yryxL6@62{348pR(l5|rYwQWj_Ftb>Y>FVU$*6*mYu)1c z?4Dd=Fl5@`Twf3utmiB0cjkyBzb1=`wqEbHo?FS8nCzj6&A(AL`d|bL2TuXO{HtxC zwK!2spEPYitH*0}nIpYU=YCT`KghB!2?P+;vTX}#`4)~UrJHQa$(pZUEjQRdnTrGG zhd%Ya>EC;YB%{FHZzXW=ozG&nYEbg_G?!G3CYY|3r!cLuAZ~6+_kcSss5Uaoac}<0DAJ@!hERv!f ze7#)o1kz&vQ&$64USY4A$HAq$l0pFJ7?n7E-0?tj+E`=eP(zg8O9G?lmaoa(Ag^KU z@A0dh-+%Av)!!Z#T;a^B7Q(C%3-g^yT=?$|-vcee*if+?kG&Rd4#1<=szWPD2dut5 znmyTJVC!n_ddP1QQ?N&Gi-ze$VCK22v`G`8SJ4Jtr~qf6LbwGdpR6!~PBQ5$g$9P9W}D_;vQ53F{XV4I=lZvp05hKVPYSB6`K`=-Hii{nqfuc7 zVq`LQuI8?WC5h95Bz*#=>@{sFNxZU;(RXGIR$|$a`Iry*sM?9c8&_3tffutnUD!}f z@9ODf6cbJNzbU-fl>VX5m@%8^T;o}j!5~;5NK#y5l9Cp*VrJS%WbG)AT{m$Cp z%FyfQn}yMzpg64+{o+*(<_s9=%aXHqubJj{m5RtQ_ND)M75CMGv2Z`xV$*l}6-VsR zsLS=d^q2_|;m);i$9r*i8qx54+C+2NS+V}KBz@(sM>-U2lmbw3`$ zX^1%XL)ZyvvS6&P}VN#9!0P=^%}drHt_}dCkE( z(q*&cEi+frAC8u`PO+$${4NV4^7*46V4?k|({@1R)d)+8nK#r!5sd}Y+5mYAY^v0` zAFI#v&IU-BM+nUKhe!yc?Q3g*ZK=U?a$@1!fXR1!m#DSi?5SmIFka93`f=?iR|@)L zBHrfcs9cVyCWMLn450`#Tsw7-zh)%w1i$gf5bZFVRj0}BSJM89;`z6 z2?4G#B)E%lW=hwBL(v0|(Aw;ZKmEby;npl@K!&1v>@okYzP(kCs#~>nAtxV`=-%?O zd&ITU;yJQS1pSyO~!P;IsXhUz2b1>!hW@;!(fN7cECI1?f)5r3C zNtmQQnSil`ep^~O?b&OZ94h?{UQ|+4Q-1eC$^lc(=NHfB-z~ckQ+LN>%4CSu_3lZi zqhkoWSi1p%fDPXM;WNlwVV#EmovDzst+no_K`^$02$1i)N(|Z@YX&B zsV)G0Dr)7{yg;Y&B$(U8_e8e_aPIH;sMPWI)fcV5ZR?iOm06tfzW3%^KLX(FaKfHx Hi%t7Cl5sJ8 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..965f22c0b35ffea7d35669b853eab49fbb4179e6 GIT binary patch literal 4572 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 VKLMJuK7A(ul;qT9t7P5>{}&X0L~{TD literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..d758ecb1a935b049780790572ad076d0764b33c1 GIT binary patch literal 3212 zcmZ{mXHe4%7RCR7L?8r0550+W4ZRq8iHP(LN(?0+0!E01qJ$1opG2vTrZl58>0LoU z0w@qV2!ixS2dRN&_ruQYhrKiRo|$v!H|Oh#H8({<7dz>2@=dSMR=YAo8JX~H`7Oo^Kr)mvXR9Awl z%PC94;p%Ys`%ccP{{eg+y4}YF{C{9Fvy|=vkoa!`?!J$^KMw7K{r^mSV$vu8fW3|M zb*w#quBYF(w;AT?I^UcK%4tU<@^kS*vvltYquj$Wi^*yW$uA2RP5g^{EAuM{)LX|?OBS(G9Ll^!&-#urPb3A8)MjeuJ}BRa_TQOHiGYplTA%MCmfD>klg~BO1Z9J*~y;PdHPUHAm zcI2G6H|_2zm}S_U87VeEE2A-1BB*=p^hnjOZhZ|=CS^;C@3z^ znVb|$v*F} zLPs-j4`Z>scvQNF$#*n${gM~MZ3)ikp)POycV|hR+RXkhh5`V7c$8(|?;{OvXe@j# zG()eS@dWHgtx^(sFV)*eq)?G|$~>|dSqlQtMVXoh zE^DyFLyuvCb5-O}jjooI%^y+k#q4J6{qcAC&9pE1dLF7iEi+Q4xRNpiK?Q^jt=Y(u9 ziV1F!ZJha*-6JXMw3eShX03*dg)zR z@{E@c7-;P9hep$%y~svq69ws8UER(VJ`OUz&h`Lst?xnoTMvmKjB&7MpDeAF7ynQH z8#(6#78xNM;~!!Y&4o$1Gz5|i!H~o5nK5-Z zp3>cJHi7&32BjSRAfaCbIq!p^4+f|)%RMDyy3!kdg= z!;~H5NMWY5S*y$0;#|J^b+vxA#sOQOb5zROi)1_Xr&pVuuH>j4SKn!_m`s; z{fBl{S*iE#d3j}rX6$rH=h)lyP;Wv{kZRWmmrJVS6)k3Z7R4&`?!Llr|H`hCeWe*Y ztsJd7P_O9N*xL8cD z`(^1(t5hHgi;czw>dsWmZ|KylE^|m{=Fd4(k zUL?Ath$TV3WV*Dq9VYwoJC|=-#x1z%*>D8?ZN7s*8nMNmU{7KIrH93NQkToxhfjYe11?)f)udM_!Ecm3OoI)^^i96zz( z{F~QeQ3qTB#cvh#HyenM_w#S?U3`2n2Si^m(GXK!hq zBNuVqYdF^J<;WLyw7nvdIce~CXUwGMrF?j*T^n<>L{yZkTuYR0s4mP&M-Uw|As}3? z7ZujB90_}4cqWq;C7M~&b}L7;eqiefdzpw4s+d=rzd^2HttxSMJWhrH`SIoflPo9* zPQN}ayoISUC>OH4u55$ob}pD-PB8hg%X)V0_7!0Um0dAUJtywBIIqa?<-mgEDGBWP z_`087Ld7`MNq7sf(dy^2gq8Oxzscr+RSdK?U>)kT61Y6W8pts5^!(ak{yi$XW{Ivx z27TXsujX82{$O#tIF_U;7oZS+gYC||DJl$g5WQ^RL&kI)2>VVjjqyfhBdea|t zIW{eYe-T}5XP|F2`aeAr5W#YiGN{JW0>3bTpGT-{dt(bx`KTjzR3{DnsjNVc%I;?b zUf`FlGi``wEdHdFf;cs<0OfSh)84X1PfGg8lCa+i#Gd@SjTkDdZse+F@*OL~k7{kD z>n|W~W7B3hsIYC(xIoS0MyO3lrLUxkp5Pv>qkMH&Dq8RrjdGyuw&q6b7ynmFpr<;J zENmTH;3(}F9Y3QNd)X`r4!R5ty9SGf<>PE4NO$d*4K zI<5I?owvQrgn~b4b21TMG^K^$1NSoftxr*X?Ff#)nHiyc)=t$?CTnBM~(n z9t5O#Qad?P>f1==Hz(OdFv28DQlB1!1CBr3?dwq#%uof+K#Np@MqHmcKR-UBK)PPo-**2oA>Ww?B=jdlz@(7&7L7~Az^ z#o>PbuNZb2jYOdeVjWiVWax z1|V%nVqo}AjCACwob0H&X~~(%%k zDrEmXewrtdGS;C`L9H+)xv8}wpAg>2m!^)UYEEL0_YAf!<2W?SYn`H8bli|9lm-0x ZWkayICC%#~g^R8Z7#W!Am+IaN`xoX%`cwb_ literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d7135664a070c4e329fd0cb53e4abaa585a79844 GIT binary patch literal 6269 zcmZ{oWlS7U*RAQ4Qlvl&1H~!DwYW=hceg3U85rEPKye=&3KWMK+y|GUgS$h~0>z8l z$NT-b$<4j*N%mREI%j3){5d;$BGgo5u`x+7QBY8@<>jO_o>lEX^%n5Ft8XF@&+@@c zPD2?5#hVTV<=b}@l-pChP0*wW>-ijyP;anwr|BS~eu=CeHAYhmUi6v(xhYKEcCzz;Dtr)D6BAQd3Qc^TvSwKFJpBQ# zKH7q}4elS2bOl-O)YM_vG977ARpv!us8fA_w9Uci_fP)%bODL}T3w;|Z8gV_(I#SS zoneNEnGZa(dvjq1gwpm6(+wkEsf>wSPeYQH-je^VFB}YHdwkePy|MrXN8~akb7^-( z1LE^*v@DzYi6MYPmKbiyvATTnX{7l284n>`nT^RLf409*#D#CIU9eW^oG9}q0YUTr!$R$rJnHD-^2Wr2LMw*3- zDqw_A@qu|GXFH&xQ5unYNDLg!Xv!}kZK7BJDU+_DdK#z#>&FNo2X8D)WgTZDoD*!i zEVk9}XK(pE&(Dz*H6PGP2dEYABt|s)StRRkZVzu!w>2M{vKG0!;!IL}sJ~FKFmUU* z{E*yffil_NJES!dpN|M5KsDAX!h?TGTpH3|6>@U}=TgI{QbR?W_-&M@6#l(R7&H7* z1F3nGW}PfLCp`S)6k*r&&-K*@XnEP2ztG}g9$GiR*p2tKbBuPMJk$v<1oQZs;IU;= zc`FfuP;AjRBxoCN?h!?jz|ZR9(zS96MG_MVrUp5cSjWAk$Exk>RU1L@Hh7~YIlTbM zQFqTmaByreHuj3BrkK6D_iMM7MWQ1n<3Ve#0PXyehnB14~N$;@- zhC#_i|F^w6Uln{6X<=Zc#r}R=3h?wcyDP8zbP?x`x-py|*@Duica9T@&ub z#abC@xyMZV%n((q3nPbR8gAOv+N1Gp3E`X=azsna6Tup!$aqruk=-aXeWU zW6dKIc@jExF8s-zMbA@4)T&w*z<2YW)Nr_c=h#|-bLNwWSym>qfyp@uN3XH1QF7v` z-;>~W(MCm7zQ*6fzi|qItvy>+gsf@&TDBCqnAAyFltStG-lvKG&YA(ZsDd4?)_dP+ zzZ?ySJerR3-I~nkCihH(_?z-xrO@jQSdA8!6{h$pr_%et*WMzKyekc8-$qk^-pc}B zc4lgNqGs~;WsLe7+9`QF#aNoTC>AW~thkGK47u*fCmEEa7$40F^6A!fcEPiRk#j7( zf|=<%-=l^OgWh+g#Stov3||B^c${~y7VVID(*((0XxuTUr0q)@%%Lk)xRoI3C}={Z zK2}xzcIKP<^YDR_{`-&R!Une{N0O`lhI}fSkk!9T zqISiB1{HxECq z7@yjwv(2yP;Z`bblue3RlaT}YXk(xw@0`pUbh0;5OA`}gxBlZo$gH*a z)h8nQRvUhzA@mOix-nQXm99XA7~QEE4T9pc>3#!Bg?TwVmAq|3=lRNdbw{cPM*!ME5 z5>%)Iz!l*lNbGH^w}@MD5Ajui-8v%i{96~(rLNMndWmt)kBrPzpA)?a!w|1_fgO68 zYT<5IVkH_vyVlPbAqITBOf4d^Prqu^{Z!ubg!{upW2RF3i)naOkM@?|>4#3_4NJPN z0*`Hv*2?vgMD7M4eT_SxT{!tBLffLX-BY09F>+hcEz<#23CfnFH=tRgM39@B2GY`H z(}Tv}5CouHHNG!62)W6}5pLi0w09XRnFJpCS(a*}vU3E_8AAYEGiNUkBD0x%PmrQv zKF2M04ZLnD#xpC3RXi(wPfLf{7CLi28?!xKuH#T{hqhp!RQrYO_gjtkR* z?7D$lw+Hab16V)B;)+gTD+=Euztn600IQnUMd^1#kc#a_Y=^%q^wPsZyS6&SAM|3K z$(0N^j)99$^BN1$fn#~Vy~jlR`N!Dg&$T5xu>hD&e2k~JFQQBsc)W6*oJ}? zUL1B9tV<5GdTK9zIanq)vQYyHKu#NE9*CH<=^m7|kt-miNX{AJ?>{vUz{zGvs@~3?1R|04EFBSP&dA*n?>WfxM1=vhBAGPW7?xU8 zQ05A3fRfyj(nDx^^%w^?pw(yehzl}+5ju{=&F5gQmL)${dvYK-7wBufg2ekY6)n(5W>)NJ-T^DmD+!{+4-Vws0^2Iq>rj4a@&1>TR=?aW*@RRpz$Q2L z$xH|LR>#t|Z?vELPIc?&UVM0b~e@(=k%FwF~Fl2I~%`(v}+21sl2)) zYGHQBi?DyI`>cx-iJta4+OSpIfNn3QAAlB&wv8arWR<#ob5>b)-p(I4lKhhzqI0`%(KF;u`CBA#AxK}KJ zAE6(1orO_Yob;*xJW#-JXYd2m5wF)%)U>0pjaS>2^q?w55X`>uue8_`@0$kTT2j#o zsc%A0$g)~mWFI}^aImnR{>yQ801pvUILrdteQI-1Smz?PWT8$p-&d1B5&Pm2=A5Kd zQ*?b?B@DL&v!x`C_@@uCHOycAvHho_?a=VFRVK;8``3!Fq+v2kT$&<)Zw(rPkAu5r zxN;%+vp67)H&}LNJt~9k%FGmIQ@0}%fGc1J(uVW9Ib4X|a4AkIWQ%aHJ@skYFnuZ4 z!ZPPhyzZPGIjbquHqaUIXRM}mnfX}TP>TFINJAksTKhaXa=8+f8s4*w zsqJh0s?FlKB%TH)x4W4QcBpPkK*E*Qy-&)E0c)dKeK9r0mTwh-y7CTQ->l!P#y9It zj7P{;u>(l4v;>b~#R#23^&y@#q9VqQ2{=Ci(@ zmqn^_ICiH_n%l0nRQ0nOpDN1#<5{k-j9;Gy&_MW(R0%v84ZNuH)_Dlr4L9M2&j>e9 zw(MRC?&(5o7h5qgego%yW!@}of!=xYsDZbJ8f)TqvRX zU&N)fKS4FS@ywcr$I%Y2&*BlX)F09vcIq;@Vf$yWdRY6oM5QhjZ8AG{SN}Efj~>zR zD2XpYH=zpy#(XD^Cg0t&hBPK}xmVPznQ-_jbH`P7^Tz`b=3EMx#7KF}lMVIAH?bsa zH?^&3(Hmo5fqFRWdGY-Nzh!F{hHig)FdgHI{}rgf7N4{HSO^TQpzQJm^~ezHeb#nA z)gZp#cU1nz3{=S55=7K1M5XPk_bmF(x_b8XlY3o?Ew1WdaTl^$CX}U6s@;FStem$3 ze8duiUIs$BbtMQG=ApBUiYvLd50?jbTkCsnA-DJ7L3 zL5{H-*rsW+V!Rtv(rv|{K z;=U3#?fffg%b(iD1fig-{-gI2t-uv5Fu;pVhPUoYFiPonay@{}Q zh^d;ZmI15oztmSu`I~r(%5Uzc;x>CUBjbo0d=uC{EDEo3-A}YE$lx0G>&0!x^$y)9 zt$V{7=qucDg0AZ|k=&T(d8=s*Go^|pI%nFWI(%6l%}^VsP>kV_@M~f!F;pN)Bagz2 z^V}gL!~4tPc~*`}1l%pGImEy%CW8NQTVaiOgW8`}#Ejby17Hh&O8dFY|8qQsx7(M8lBIn7sj?2b(JQ;cKQ8EVx9 zL@sn&%SeL1=A(<{<^7SWZ(6cjBIj{cXRhn9+S1E;W#ubqhJu^t4toX8Kr#^PazYL( zSg(#xxKQBxa^JjByV*|0CdS8!;tXSNTma|MpOOuG;k}4*9*kaBL# z(MJ{iQWTTce^4X8n&<@mKsyOV1y2wO`L}w`WPy|ROvz~lg-2v}N4}gM>&_d{Br?p5 zyyINpLN?BKkclBnoc4es^yTB$mA8)gS=Mq)GaN|5%tXLyyj1~a#xg_1SzsN_3wm`L zXwQoXjYPRrBc)kpqW&Q)jo-pX;^$o(-N9k1PU>>J6;o{s5~w(C@7oaD&P$$X>L_HJ zz+#Ctb4?|xkS0|jO-uX2Ngm%SkhU$WH7!oof zh6qA!zqq4|%BX;lWll2-Nc#QUfsL^>N?S-0B1g``GUzw`Bc}r0QlM|>?m%h0;S=cG={PO;qd#*5~aQzBLM*o5f;B0l4GlKop zxGpx9pX*(s4P;s#dmri&jC(z>(gqNOd#wVFf|F`w?#>NC0ljcCX9qn

;n!FYyJlYi^ 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 T#TWRG;{%lB)Maa>EyMl~NMEGu literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f89ee65429bd4c938d8e8ec50ddc990778d9aab7 GIT binary patch literal 8360 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 T#TWRG;{%lB)Maa>EyMl~NMEGu literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..cfea5b3fa14761b9cce1dffc9b0604b89cf344bd GIT binary patch literal 10625 zcmd6tWl$YWx9>OZ76>jIcXxMpclQK>yKmefXo3Yc8e9{CZag>ycY?cb91hR(;hYbr z-g`gYsykKF-PPS|W>t63tgin5Vl~v{F;Iz70RRAoqJoUpTi*1~jSc&j{uNiHd&_{< z3R|6}0nV&iD%_n!@xb83*^3>g0V4W5q9wjQ3A&aVI2CLt+#{>{Wv zQASe7cj;u+J`itq4e5OS%a>JEbzoQo{5#l)nCsG}N(R}lS~BpmIPmZ~5`D_8R58=< zWn>SjnScfoCE=mK7_E{YG!m%TpHs}deeA|x{pwrS7`$K?ZPTuTAkkRI<1hB!&>!~d zUo2&0WU%2Se_DU8M8k%MJjZOjr5re`#i{Hx3s2b{;fna46e(y5uI6hZA#aQr>(+!GzJX51283o zrY=NdK!cu`gXAGt4~ou_P&${&ka6+CALYDr)HwF3h$NR1t_7laD+ScZPnJA&lCag7fkJ{>M^K?)(P)4>H??9!4E%8$Y;s`JNh3bC z)Ie6lHbdxxoJ2JyC~vdf{%!8Z?GMq~#1xB33uWdyBB+Qc+$h!(9i- z*9hNSKKYT3jNM@KoUaBS8W0Q;v}@Q-p(}Pe{Zm<+ca~ZU!`P{mKiTEQP3d(cn>zA4 z)km7mrnF~PYuJv|TBTk8nU|ye7nb7@*C!4fcpv*Hcp~A{gsVA@ypL{AJ~C+zZYyY! z;Z-h|Ef0Hr49%O`ait<9k` z3H&7F#8E2bjTuyAr`Y+~ns|Gqq^_M*iT3L~zk{1Vsr&o!kvHGc^$;e%T~2L7x_Tvx zH{VwATQf4|;PFIHg$YQ*=ywY6;!k#XSdtl*K7l9mDK=>m%9a(hOatErg2{aS_#EgH zzJdKwC4wuWbuihD6YK9854J!}5ujB;io78OMi{mG?A8UaneP^;c@IHH>)AjMkLW=Q zj+8Eg4Q(JNO|qE?CDVI~s*JIhUVuBMgi`Kq#WY1$Tj?99CfLMl;JzULdL>EeF?DO| z;C-yYfqZ{c?gBq6!nvS!3G?u~kuqH>Xke(CY#<=e>Cc~v*fI)`j>M@^;WF-4y9W`8iKZ8@u>WTI_&eC$%dIEq zOi)KU2$x1d{&_8v{5Hk?HDa~KVEM4~1E2f19zE|l^^8Qi%q?XbcC8c(Vy5cUwufz!sRvp61Ub*Av)-c2`rq|vSzS)=UA z)Z1da1;tAZyN4inetnxysZZ1-JzOI0cvw4-o)Lu1Fv#OHIjP^Ig?-jyqf>54Si9+B zcxQPMVhx)zgEPx=N$aquqGdof#kQ~?&9GZHU);f zZ63WyRed|<31v5dI8PHdFIn2kCM0>-%M?m1+ls109)yC1@PB%iU~VX)r(GEqUl)S& zjt)UedX5xA!%?UtkErH#G`Zcq`^Sf%h-np}=$p|vXdUGE&`8Nw-PBY9WebRX6oxz_ zXY1R$K8t`-Vq~rdhcVovYyIYufPh_mW?1JZNI%bcLp?{K6Z~LD1_E@sf^YFwI>r9JwO65yaKC%t^a@lKtgcz%Ygq@8#U%Z7NT2ENMDkCM#S$`U;WM z5$GdTyuD;k<>BQy`&Ct!bLN7_%T3{e?`4j9t?wv#q;++?yi9veE`mg2`N?qSO0Abm zv8V_sYuJV`VmBD*T2e9Wq)WuAn2%U_?NF-tK1Lzqkbc@!uSn(zRUMF;g9V(wefjR< zMaloyn6zoIcNvW&!jKm=DG`Jkq;{1OXR@6M&A_1TM#cE5lO)^dKP{Enr5%ZW5& zDB~*doQ(&5nqA$|lkqbCvJLi?*B@%VOkT?{W}Vz{0SGiT%~`(3%>MGKd~y~$rTiFqe;V>OVy>9-Hh4+onpy2(zgcGq*fCdI)Wowt`*_j?IggF(%N*Sa zIFl&bY%MNpWSo$HpIP`}pRqZ7!K6*GSayt__c*E2&SuKX?{#yU?nNaqj7SC#3S6Z< zd4amIX-3vUGMe7gi`P4g3A!U}g$WX*Ar|4}#GD}a@G!oUux%=~IaIII{3f@fK8;($ zE2y1xJD!Vwi;XqLG3d$7?zhow76C3*?_dw_?hgtwm3-1+Jh^+N3djB4oBrImYF7&o zzO?X?vT?;*dx>)ydDti_b6&VeVgGu5DM+7IQ$$D22%Ax-L=1~dHARk2=PM1ulG;qf zbi(;iAE$-2)Zg|Ye1;wO>oKY0neW`uqfDsUQTS#Uof8cpCSFP?3pr_e1cxXIZ7(=B z=x%FLnK7V7q-{lfw;I>^os|1!0NA3~KDRPB?QV;%9&+BgGqin!McRhAbhO&u2J&9yjvrS;!yM9*y#-$t*< z!KN69qmOl>Vw%1Z#7!&aTNvlnng6}nCf^rft57nUjkVjTBt-Nb;XN?^1aYLff z60_kNB1;XYApf!nt>~gtUvB5)JQrantSS3|@dYbtLpcAOc`Q38KH?YMzPu?c(R7 z;Xay05x~L10^($53wya>8t~m>tV;jotBZNr*OuLH1;Zhd)OIjFI%r3n(67|A=f=0_ zKGucrUMJE;8!CTc6ieX&8`k|Pkf%6kVOULhe&NR`?g8?kE}XGh?3DnWVI5{P2jUAd z|G5c?r$e2D_Zx+^`z%qM~)?fMju&^l^beXbkpG*2})4IgT#-{m&4|Dhvczq68 z&Es=<{@E>vteQAk=uWLnHNf%foF+;Co=4Fg%PahAYu-s@rq!P%d?bT)T`PhkoLFtI z6T1W~V5PwIcvlE~UA&h&RRfpa5y8S@?LNYiLL)W+?dm5)nzjlCEGVED;7Y9QioW;h z9Vb6HT!Vy+1BQ3e_t?sr0o#09{v|p4qp{FUEwO2ZS?8-RH_NynUfuY6`tiiA?j&4@mwhU+--FyYBsgxAo+$;vLsjuC&3Oh5CI&EH55Aq#~yA?uZZPEW65|kSNLdwykgsQ^O3G+S@cGCr)EfS9$YCt_yZ`Zc^I?SRa-A{pN;X!YCNUbQ>m$W=robZskZ5`Spd}RFa9+5%Yb|=mjbodN6LvCEwf+?Pb}_f%@kRV(}eA z$a9YD&$*wS_@jmK;Em`O^MIUKW2^rkB(ch-fuqm~4(Sdl^oA|=2A|0_A<@xhiomJ~ z_OG6FH_Mj>4#wFP=MF;MnA2c^_@z3~YqKiZfk1qvZ)$A>|F6bbrN_9+Lw-(m|2q8w zztAUeG=kt=rVtz;N$`eD6Bt$~2*O5m(LB|qL$Vr2&z4tyKM6Wg3CAG0DUDmiS9Hx_ z)9*8&SrUCVG}`bIEAG7SC|CbA+px`lH?g61%sW95-pRVc70y!Aw~2as9%RgCN+7)Yi|c_RS^BW|5C^Q`x%jv|CVdkZFQIwTm@(SniK@?28LPN;YrxTcgy% zDR%!_Ew-FacNz52l-K2kyFq-wU~*#5>oZvU>4=~!#>Kscg6@(zVWIxi?m=cVJex&LD86k1VUsc#4Bsoiad(J zAAF8}AtItbEY?>#X@YP=giq(&DVJbw1U|-bW}AXiHPiWR7_`h;2EK9^*d{5OH_*~i zLIYzu7;B4P4G4)2y|~3{jC}$u9IZwoLb`7I=E96iX`LswiIko87&H(4uFVln+FL?y z9Ug`*LxP`9GhV{j$C6ark3B>@EG43U@&4=Aqs8;bN)wrZX9@Vh=j~sDu=GLG>#D1r z!G?h*$MUWE&8>%JRkQ=vHEC2^UAiz5UUyWX9ZAuXe-$#WC)3||>9p~1ifNgv;Z_K% zqyXQut@$v#Ba-(vsquB(2w-;^TQgy$=$X`e{#Y>K!!sNDmJZc+lu1QX~zMg4{I$EZ!N>m;T zQWY-Fv?k+3LCYm3n%BR>>(LBP1`}N$o;B8rdAH{M_)$?S*sklTV6c+%*bhzVH-1QufzPL5!g1FQy@NWHT+_?qyS@} z)QIDnwvhq>rYe!tVN2I#kz!a=^DY-k_rmj7B{!}w{vnjLcO)YAB~2-O8j|8f?I*Ho zzm1a_sE{U_D6TeOr`3t8PD?9}TEMEWVM|OX^2Cl&mB$q+5|v^0ePTah4;b*dH7m^J zO8giDgirPqIX4MBgw8~zVL3ksUe$R_RajQwZdW)V2f+P&j^Kfk3l(v{Cajg#Gp9?} zQs}^v1W-W5rhN>yeyh5thWeb>Y;E#Hiw^K@!t@oPWNsn<&GR5!;Fyu49&z;hzA~(> z&obP5jQtqP`-+X(Z|tH1YY3@RpBxJEp$ZQFv!9hL}?K&_( ztRhkv7dC0C_qzQv!R;C8YO5I!CYahREI6%#Yj!jF8!UTA#&gpRZO3Q2O%xqFO++&% zvUdjw_xz5bR#3ZPEe{t`NxGe3@~TLlo;}7t6uVo1(6YP`(i(9}wc61IH&);0*Z$j> zAaQ!x=jqPmn;spO4t6Rq0|sd>S)aD=REv^$S@Pm9+JnE5Drc&t^r~G$oJ;<}?w7*G zC8{pDxdo)DB1ZXrhz&T1a^gDslZJGq7Ns}ZC)q|JlC>R3w}FN)3G*_w#CaNMSbsq- z;}uo(xcJ6Md!1BaLETS)ZbL}nXqr{AkZGw*-wbm7<6F>q_jQw(Z)!>6)r`z=ql{f& z^c)$4QN6etr;KO4L}mMXD@cv`>U{N25$ctg$G~+s~w)u!ZpxbHhIA9aU zbvIs63_zZ?&5!3n>nqtBO8==K{l52Fev5!(7>yGUC)cfTJH@HUCbe=CbU^M?&#_MYbTmTIB z`D_pYk?bO*u#urXl1OlDh6tMa@tN(Ab8td0-k9ulWePJ(gpDkVUQxbevp<*JIucQr zcpS6AdJ)U8&8Twp-g`dRoW?oM6Qdqwwz*{v&d_Dq+L?FJ2`aJ-Z^7`8ac71Lu)5(S z_kitcPIz6;?c18G{#7u?oZy@R*h7pi5fC1*pTa&=^VolX3C<`Fi$ctQqP5aY_SV3>FN8r&iMR0W*>g#D z3$~e&01VexIwVuti;tS5nK`b`-8~uD)M}VFuv@Wj!BzdNr3Nz$M$`{-Vp7<5*xWYn zd@Xw{xE{YE+5EHm*uS#qH+Xs5h~`9X?DofH+_+IHe7L5N%&dvLp*fdh<0|EpFtvL zdA$7TeydsnI?^7Zd_7y@$k!rRCQE$K#u#xOP;aUmEm)D$lhE*7q8o#%m+|H`k%Fa8 zLSAHgJoh(gu=D!BJT6II2;;|=jwoj$lD~SXnmS@S<@@n0z6doJ;>Y|YpPGrUsFzs>1*;{Cv z@a*RS=Df6Dgag^Vu&2RmI-Rz4pbDB}d6<4~w5+dgK*3)I?OSU0Us2e;J!P)`v(@q+!uBoT1ZLK+Z(-}BH5Z%AqWB-NIe|xP7c1mzW3z)^9HPVuER2i5 zr7h)=41e|6T& z%(UHQePG-RDr=Et)WocMJ#y^__N$8aoJ_Te3kf}v8^iD(+N=4IDAYC^J9$HLT^^u!5|VMj*`-%&Pc*vUZWb`#XJK@h$Zji~ z)f?ag2OCbch(}XS&!OXR_3?cEdD@pMZ9>i{<{CkXXHME4SEdL^d>fpeKMbd0*>=>X zcNYj6_!eS4K2Z?KmQbTD8c32j24vg!Q%ll2XU&?=!j;VzP~>o!m=A<(FB_P9%8|I9 zhxGlR=E{dSw14Ex$;sxQSv1~KDAu9;W>OjI6jxg20Mn2XF9Ns?#Q*%jDc%?DBKJ4) z%CSPZTK=lQl9eNMtSxP?0q6^qUYB(YtThYQ+J|qE?)Rf+PX=x`{6402(om}pQGN&Q z$?7M-j4+SZ+hp}GbkmxAN{PF{L554r3Ft+~4hSwqR1@#5S^yAtHtxH!}BsVj!OUBZPnQSl$iI79669oI)Xt^=m9o1~%# zGK=7j-hvZprGmP2nIdgKU*0HZ6KS|FO(8+P#L(%VFmjzW)J>x<5Ixzr)gOr8tw^0$)tR5HQrgj%Ie>6xxZg~F)7+=?jJrNA3Q zZ?}tR!>LLw&oPN9`IP#WPCx-#}rXYNyIs^hbAeP4u7w-U3i3}1#P%Zkt@bJ2u z(pkCQwC{3r=c^91M)HT@vC|m$Yl(pJYk|l0bAPLsD$30#;5VOsdd=DUf zA0(>oy)xAM8;RZ`Lv4$|JIrf9V~*S&P`nmSeZGE{G99QGA|H6%Ek#Fp2?*Aq;D&?aSaovDXczU@YGqaxM<4?rvaxG{N zU=z`<9VhI3xxEkKdN1W#b~j*Mx`i&6>cs)~ie-0dt4l?!MBHEiiT62Ya(r0Fnf`I$k6(CPq{Z2oNb|s$~ z1jcnPcJY<>FQ7}2`VEf35B#R^x;W(L_0d=Ao{BkrZ5I?8NITe08~sVtaKA6JrU3_Wws}qZrx+9r z9P7U=7`$XL^MAC{_-GjUA^zS{)rA2p=Y{ma@1^i7rWKo$%zw8)A=0+a49Re(Xn;Q+ ztP}Qk$9uvq&fg4s^7G;oufCPQxO?`&*&-86WGNo<)6zYV_Q-8AGwU{@BE*S|=ctwL zqDF0`(z^@Zyd??c92eDNGaw@o>Awm(`*5N`v}VtNo%iXs_=(fs*vs{W=p;p{s-Rx> zlP6-mz_FVWre06rbcWilQq-YyauOi)buH`q^z7a)8a0E}!etACFSXgCatb3=tsS6R zLD|8gYmblLL*6ST6S|eywnT~)mVSBs(feex@;#2x=$OIS!=91TrzG$0&UjH8Al;uci9GwT!b#^Jm*WU)& z@w&4?FQRuu$SX{0TG=>Uc>m94pvHiV*(5s8PQ0O{Vw1=}Gyl7tgQ3vcK$@DForV!h zkHb((>bQzSKr{u%COHmd6E%qlH7S^kv$evPR8;4-yaY%{3+jDO+DO?y3EpVw<01La zsy^p;Eft--(#bY|LYJXlU)$g$3JGo*;Pk8)%8$YDaykSpFA>RRj*D792ndEsAdd0! z@p24yzH@g~DWGqFKnvcshzoY(?C@8_GWer`JUf3bG1O_NvZkirGlCfsP*u5MLSp4&4y_8*V7JO)o0*(}vsCaz5HTZV>6feOy`<9!{zz%m0Zy7}j!) zIpRa6O)N~vwSN;KPxFo9P8r$pGO&b?YuI}7(xE68b_yv6C{7Tqr#L0TnEdiMq5hSx zC|ui5`ZE>*yGt1K{&@~xcPvc}IY1>ATsen_rteSjs(WyRoYc~V8Q8t2KHk=fcJ^?b zu@ubRT8!Kr&<7^?x;A;-m}OSI34=S9VFVN4!6CWoIv6yfb0)FY9MxU2rjw?L*Wo>j z-skEq3MKK>H3KD!PN%F&AKjnYo~Q?R-Sjh;Uuc^g=g(g@JKt}Z9y1#dC`qt!-D7@- zBvYdMJDS62FNIU11|x4Lr^M=AB7avM+s5<>N*$&*B%4VkuE(irYgaL13}Uq9Qpex; z*JoR6gy0n11txD$2~`1aH!{Hf>_g7FB~|`(SK0UUD58Cki%Cb;o9KA!7SW3YlM8;o zQ!d|Xc((thCTbNAv34|#1k#9?IhUFH+4D#M6ILOEC+W})jc(u+S6qpB_4Bn7G1!nW zQtuz(^F68RJgq=<_}s5wiXBp2f6x!(0oj`ehkxJH%GZj&lc=ssVrz9?RI zvjZWv#Q5vh;H0vQbBb{z7@QffW1_9v;^2(3K4koj5v0+29Qd4`SipptBs{|};}>!G zG4=vZkn)4_BZJrXl?;3TrnF_owDhwD3q}k$?8pKBbxd2yt+sNQ>iK|6+$UD0l)w_8 z{#m|jSvfarX{0~lSYh%chSvdy+_!tyd{=Tog?VPJcZrR&lV375&Qm+pMIjvr(hXoD zrf+eTx4#$PmaKrlUohJ+WwzFV2eJO7@rWk3Q~d*Fb#6s{ll05)>%{9XE4oRR5GSgD z_%XXSL%4K#+TG-PC(eh)UkZt9dTx?+wXZf41){wr^NYTGYqP?1D~$fA6kFD{N~PaR z2UXXZTb0CLko`5(^QFr6IkYrFMS@f8G{s*KzZ{U;Q43*?=*y zL_#{Jq_YwjD++2NUrvoNbBF`z$ROPHww>oU+j4P{4u5bpVTTn%W_U^igo>4DUn9M# zVyNmk@8LlNBAbqmr94`PmXw#8fFzOaHh-iD;Q>?^-DlcPAVSEb*)JKQbQl+ox}Nc| z`ZsM07Uj~ERhywi#&snI44#1{a^EZnDGPTHJ1KqRSag1VGz6ex17etyi8V9jbl+$w z`Ts>deZnSHd8s5Y!B!IH7hGNi3Z%q#9+TbmvOIJ|Y9)kAu)uV1AYy2|yqIO5Awnqs zMUZ&pWh1h782P0b>BGW)n;O2IOhS>R)BizKo0V&tW|0qcI1=$T>nN7xq8Wh zT-EcW=(LI%hwMdFh_u>8CT0;<^GV>5juL$8mj2k)%d6mXc4S zwkGbe?GV|LivZhtrLg(;w;0fo5CIP#mgJK6STxd{yqgK$c1n`}kV0E8jBg{0eHseR zsK-$hU^ch2oW@&sNV8Gox=9x)etx5j-teJ#@KAQk6VJyH@4GqPFD$=z9a?`(ldfr4 z47eyria7mM``8nt2p6_R$d!`N`!i~!)N-1*HAh%3DqOfp;1ejy0-L<)UmEFO+~~n= zNxh?-{aIQ=It9r=85(IAkZzTd5|C~LhHfP< z-+j32KHPiO`k#H)K6|foo`11=I;tdu41@pxfJ9wQMgLK?{xjs5k2{?`gW{vG+N$Ym z0|0@X001l!0Jwg%U^@VSpD+M`wgLcTvH$>DPekhrxyK7!8%N zHWUwyLy0XyF(_gY_Gmrvg&OhJ(0sz!Q>MBZx z{__Wj05c<_-#^0k5V8PT@@x(}-%sOAgHL~|Pz{0`r%$?)}Q+xpO>C77Q9_CZE%2!t>7!-YD8~9Dn@Cdvcn=7vCAkT_PA(OU&MX~Mo`#bSG2eLC)O{oJ4^E@ydo3!ryhF-8sWwX|MF&^nJ}W~ zIFcstIvZ3l*!o023*qVyJ!{rszbdsr6{5XgYO66O){!0oIZffxv$xgr5CRpk_Y=V7 z@xB8VKFc{^HzrkQw>@G?o>O;<_)6}}LGckpS(vfTh-Vffq^Jtq7y^Gw5aRNLKU!wI z2Pe4zeAv{p4w4K|xT|EQ1=)XXTx z-l*sjQ514Yi0Ee(gUh1E=QdhpX%{rz-VNbH&fuRK2J{pcWmqfuJ4 z(qq_;gaIJYH6yXMb`k>gO5$e^spCkKehme8kHQU>lm7mrrnm= z;nd(={2d!Z)mkFJ(Qt2sr5$e=de%xnA;Qb209#+z8AG>Jd= zYAF5M`MG+_hnAeGF95?hVxYR^q<;>sNB=tHV%}g0k`aw}J|6EW;r+>Jh*K~U&n&+N zSF^ehSUkx)ROI?Wu`&OxuyVNm66v-4*YXs%_1Igt4eHREe{JHRouJPiOhoN4f-udk zCTThXfO7HWUk1jSk)KD|@6HiCds;JQrm!CWvR3)?pf?6!C>^bj#b>B?pxD9XNRyy^ z>+@4$y41ZQG#w^}ajczx0V51^A8R!+2!nwH;K~+?4(9*6zf&>sDwEtmg4*zFbm*8n zF9*LKa-1iS;upp(VB8Gx!+zUnAYOBMY`G+vZo{Q&p4 zO!|v*AuV(1t9KI?7gHDZnSCkIEtM(J=d}*ZKZjX7Lzz@xXG**VU|~CB1Sh3VALOj~ za2*vi+;bu)jks+#s+V?$tNnu;_w|>hf42*bjAhBpKM_+Fu;q7U*)(3!`qZUb8tP@9 znI0kB%AKba6sR@2MGRv5!)LuqL?-k~yv0>F3?{5m6p0SqulItbc(a)Er%V=3I2Poaq>rfj`B zyPVZ&u%rI_&;fU%S+N#V36LjZooDV|G;y8V9%7VT;wK=u?l#Y~kx(F6 zE!4y$?7@ zJmt0k1i!tpyaKL9Q6rl?UKS?5CT*vJ4@Z|LH&B^^pFKkFTH0&g+7*E`;uywRQA&lz z<{#2I$WUW%UBF6^uyVCuW(<1{7s)S6t!NX**FmUoYD{7xoi(aOuDlGYV%jChEsS0` z-Ovr+m*(GqcG#$Y$v{@axSimMfS-XZU%2smJ5=NEdn(Hf;yZw}g@J09iA8yKpVD8Z zqkw+r*JxreSZ^xQBTV3qyv%x>n9rHJliT=&0Vex9aHST3M+5nT5kt_V+!?N?$O@Zi3yN=Y$ z=GMrCQ}LX{w>wef0Ih}z>Z>Q?jqmCo?77E-tK_{Ou9PZze+*y!GB(Ae#>5odNuW0s zFR~ajSifse4t;}Z>(!C)Dix-}WUz4pKFCm=c3qT!E?Y^n)B$<6zkj>JaC#7^GEe3} zD7uY?O4**AV6}AAixfTI)K!RqDYnkZMU?DBAj`p_wne<**8vThk1b3T#R^0j~_pwhZvxbdU;nLoS!2ZqlhA7i_2hwjE?bxCv)+G0) z5!G;Pb&@S>re;U?>$Ji0=+2Hnc>!bd0bB)bp9zpWa#8rn5=)O0tHUXt9N&F$n!b2(#kzb>o@;YFdterJ?^pdb$wQ{v2gxbp zAN0H9{H#1AHe`Z|VH69{I=x7RWktFz-kxqd7i}9remZOx+a76IvXI@-Oz+xEma&GB z&W63gclc=tzZ?est1TCSH!UBGSot0?t`wbd{9aKemF0cUZQG!A9lfan-}2UQFpB~l zlMmpI>9Q&q5gy*i_a znN&d{P{JG0vo3#*t8T1W5+Yu$Cy_<5PVh^Tw!q6%)0(r?>H#73rzlF<&M6=5vYxbvcOG!TU9?x@>uQ=<+N|C z){b}>XifnY2IHMES_^dPDesX?GD%=MSBsj?LY)Ueo`V7V8PLZ+>J96qPp-}W`d(xg z3bACDky77G*LjQP=*CZWa%a7+UG1utl{~=DX$yi${8_UR+7hs*La)AYgLUgLb;0kL zSEFH3eR2jJf$9-@Fuadcw^@Hlxl)p~23Xb9pd5r^sp*1#Q|BL-GY{A7Z!x2O2Ti)i zBH8_lJ5ZS2g~n(ra2;;P(^~f1z!iAXai96qtoTb9l*XY^bHZg0>yNY(ytT~H5=8)) zZI{#CJAAi`Jb*&=_37==Va#hwOrJ?gJYBxX@p8tv-^kbt>V&L!(`F|Zn;Qw9I(*X7 z9LFMCg!D4jJ$qIhl@%u$?^6lS(A-NhOHiN%DxU5SZ{Ln+1Jge6(eDZb;yD;wrI$`V zV?anLa!I%K^iHz7lEII?|JA4Kdg${XbW=8WUqww0$MM^e$9+Gr4JN{LSU&h8x}u28 zZ?G>9U=!*iSawY=_{vJXO@hHdyF`&$x>=lq;m_D@4LF+9EbkA0KZM_cRjxJ^S|C=( zQR37!y)+pvILV%6wOpE{=c9RnmfEAi?qT*jki`yOBYdZvjxcG}X)_|tOtxXvPaPOL z!Ek#wHf=E#a&V^DQ8OIfO!&`1vuJA;PUT2>+UM;T(3!oWA)CI%#P@PSZyfJpVjze8kftW{{FQ|1 zOHcd{wLLWTNCEE@v`tEfSe{vD>P3$7;z!%M`EfM^%OL*l-|m@fJRH`WLLn`Ek((B? z3F0@ONA^Di9!?PjtT^bVvhm~y+A@yX5QimL-9|zHgO`j6tgt{^enTdx7-Wqk>iNiK z#A4}|+7UFAAfn1TG4~b^!zotq64W!pulQWPT(w7DqsYFarXou)=@*w%(oPrmTH-8b z0p)Tmm7inKzK;j_iNQwkTKJEkJ0|a^$W5?8(m$JygJs7(38!M$N|M}75naVsUlSR6 zsW?#)S4YnaOx03&=y3hN=Z$Kpb$RdaA~dtd)RuOgC)qF}ftB6j=WEF?BFA13vf!up z(lpM%^R4lf38UDEKdsX}3}7)@elb|BVoY5eXn?kwt>JfxSlaObxm~a=ftDtv@j=km z#Y}H?GFmmKnAnu@qJ*|eJ>}CV+I21I)@Q6?t=l-*trk9;EI3w7KWYc!)D{&fQ0{X@2%Ny7s0`=hA*f=KuAtIFJLLmv(bct^yJ1Q}W|3Hkb2}UG~ zn!{L5BHz`n@S1A7I+Nh^kbH9CdAGJV)HRncsLV!~;+lEJn^|BSYQeyrm$7Yt0*;AF zeJ;c-rEl`Ldx5Fl>j(h(HK)wR-P0{^*>y82rL`uI&N8Fu&nB=BG;KeY&m_ko&%e4U zQxQjZ$-9t*V_1OLQH~b}W|4@h!EX`GG1ytPBqxTjx~zQM*f#}|rzf};?~@NgFv7D+paY7N7y_YpcU*1-WM^$aK>^-y*|-TB5e~J>%pr-D zbW-*>HphQ$*x&tHR~1E$yZp&T#Qdq&VrqT$J{xxO8aw8>eS&}WB)maJ6O#xcB}dyX zrWcFv`j2z|WC=3AuEQ{xAjum~3j||_|OrUxdPl81^7|~5+-^`Cw`aswHi>{xQ_s(-Ptoo<6Vw%scDPDh} zc+#ylWLb@8`I&2QG&%N*cbgkBVkzJi)Z8phew7M#AbBmLBnfHJMabFA@LWv+D_@ky3ia;H_nPgMc6H{>N zA$WB}T%zLycd$aK7~pA~y~^iBwCF@ozz0+a5*{Z`I!!abN8&L&qIjVTFF%BDVWt-t zx~*2(XFcDXcBw#rSlgp&FBJ|x!NI|1YbKphfrqIt#VmRrGMk_KJ zOG1~!5jzCeZP&fL`JioMi+SJr2crk*sZ@@Zh@era{4{?ND#~FT5ti7sy(I|k#kLG3 zB|5RNm-**niU+m$p%Kt>1taFKx}80r>YCSO_Vdrh9ahc($#P62Z#Q5?G#S zFbEJv!(kXwfAF@?baeB%G+$%Vx9n(tH+|}i2Dly;!3UX7>cFk%)D0)E#j`$dQp9wd zvp}_qck3JLSm_M*{II1v#kH{KM|FYKHp)>}~4<_ixla8;!C%cE? z%{|;lL$mi|3Vwa_DbdKKl*ss_;HYxqfL3Celqfac(rXqU+mMt`ZgG37{y9493~MZ^ zZ$Y8d>n!cmGhxkBVU$M4(}IxLF*G%nTc!HK{iFf?hk^BIys}^C{97|4i+&!5Sb^By z1=~?Uh{;SrHeC0G0a`Uxr)~&Y%jl4=9y>_s2p88dM{=&5uSk#u#|}%`@8*MyNd7Wq zrois;y+^ZVvwL*jO(|z>#YvU6rZ>IpGH_@+{J7v0h~&Z~f74Y0HhV$4M?UuBFFSCR zJqK1OwWd&zm0qC-WQA1*>6Yx9@iLh89>J z3IBw5DtJFt#-057PnkhWTUNoVVxg^?P3B2X6V+t3lk?zXL@U^@%U8a_wUDovY=(TKr;(G&FCyN} zPG>{r6G-1P%Kc*q8>-3Z%m*6#XqlYHh@_L+FjS*S(MiqAUv0Gb;$e%rCu>V=dp16+h12RT7Vz$eV*?cjJ!{HbA7bX`u@7Ca^Q< zB`z1Mv?m|mqD*39@L_Ymskbrn9&4T{W{8{QFX3NTl zAg{_rNv0OHQzw5L2~A=^<|$M&l>(mc$B&S{v??eTYGs@KW7Iv#sO?j+?0d_^CPW6zSl^ix3QJ@uMy!^j`0=SD`I u%ah7S{yCe6P|nX_{NHdC@_*6OzhTGE;*WkbqOkwNj@6+$D%B9H_x}Z@G6Dbq literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..02a0ee6957a492b0cda8d08b7470f6f905b488a5 GIT binary patch literal 9431 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 A2mk;8 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8c9a69fddb67849dcd6da580311f597be2b05972 GIT binary patch literal 10373 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 Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..a44ca61 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,89 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Luban Imager + CFBundleDocumentTypes + + + CFBundleTypeName + Image + LSHandlerRank + Alternate + LSItemContentTypes + + public.image + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + Luban Imager + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + NSPhotoLibraryAddUsageDescription + 保存压缩图片到系统相册。 + NSPhotoLibraryUsageDescription + 保存压缩图片到系统相册。 + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..e127ed8 --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,644 @@ +import Flutter +import ImageIO +import Photos +import UniformTypeIdentifiers +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + private var imageBridge: NativeImageBridge? + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + super.scene(scene, willConnectTo: session, options: connectionOptions) + + if let controller = window?.rootViewController as? FlutterViewController { + let bridge = NativeImageBridge(controller: controller) + imageBridge = bridge + bridge.handleOpenURLContexts(connectionOptions.urlContexts, notifyFlutter: false) + } + } + + override func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { + super.scene(scene, openURLContexts: URLContexts) + imageBridge?.handleOpenURLContexts(URLContexts, notifyFlutter: true) + } +} + +private final class NativeImageBridge: NSObject, UIDocumentPickerDelegate { + private enum PendingOperation { + case pick(FlutterResult) + } + + private weak var controller: FlutterViewController? + private var channel: FlutterMethodChannel? + private var pendingOperation: PendingOperation? + private var pendingShareResult: FlutterResult? + private var pendingSharedImages: [[String: Any]] = [] + private var sources: [String: URL] = [:] + + init(controller: FlutterViewController) { + self.controller = controller + super.init() + + let channel = FlutterMethodChannel( + name: "luban_imager/native_images", + binaryMessenger: controller.binaryMessenger + ) + self.channel = channel + channel.setMethodCallHandler { [weak self] call, result in + self?.handle(call: call, result: result) + } + } + + private func handle(call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "pickImages": + pickImages(result: result) + case "takeSharedImages": + takeSharedImages(result: result) + case "compressImage": + compressImage(arguments: call.arguments, result: result) + case "overwriteOriginal": + overwriteOriginal(arguments: call.arguments, result: result) + case "saveToGallery": + saveToGallery(arguments: call.arguments, result: result) + case "shareImage": + shareImage(arguments: call.arguments, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + func handleOpenURLContexts( + _ URLContexts: Set, + notifyFlutter: Bool + ) { + let payload = URLContexts.compactMap { context -> [String: Any]? in + do { + return try buildSharedImage(sourceURL: context.url) + } catch { + return nil + } + } + + guard !payload.isEmpty else { + return + } + + pendingSharedImages = payload + if notifyFlutter { + channel?.invokeMethod("sharedImages", arguments: payload) + } + } + + private func takeSharedImages(result: @escaping FlutterResult) { + let images = pendingSharedImages + pendingSharedImages.removeAll() + result(images) + } + + private func pickImages(result: @escaping FlutterResult) { + guard pendingOperation == nil else { + result(FlutterError(code: "busy", message: "正在选择图片", details: nil)) + return + } + + let picker: UIDocumentPickerViewController + if #available(iOS 14.0, *) { + picker = UIDocumentPickerViewController( + forOpeningContentTypes: [UTType.image], + asCopy: false + ) + } else { + picker = UIDocumentPickerViewController(documentTypes: ["public.image"], in: .open) + } + picker.allowsMultipleSelection = false + picker.delegate = self + pendingOperation = .pick(result) + controller?.present(picker, animated: true) + } + + private func compressImage(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let sourceHandle = args["sourceHandle"] as? String, + let sourceURL = sources[sourceHandle] + else { + result(FlutterError(code: "bad_args", message: "缺少 sourceHandle", details: nil)) + return + } + + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + guard let self else { return } + do { + let payload = try self.compress(sourceURL: sourceURL) + DispatchQueue.main.async { result(payload) } + } catch { + DispatchQueue.main.async { + result( + FlutterError( + code: "compress_failed", + message: error.localizedDescription, + details: nil + ) + ) + } + } + } + } + + private func overwriteOriginal(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let sourceHandle = args["sourceHandle"] as? String, + let compressedPath = args["compressedPath"] as? String, + let sourceURL = sources[sourceHandle] + else { + result(FlutterError(code: "bad_args", message: "缺少覆盖参数", details: nil)) + return + } + + let compressedURL = URL(fileURLWithPath: compressedPath) + do { + let data = try Data(contentsOf: compressedURL) + let scoped = sourceURL.startAccessingSecurityScopedResource() + defer { + if scoped { + sourceURL.stopAccessingSecurityScopedResource() + } + } + try data.write(to: sourceURL) + result(["overwritten": true, "target": sourceHandle]) + } catch { + result( + FlutterError( + code: "overwrite_failed", + message: error.localizedDescription, + details: nil + ) + ) + } + } + + private func shareImage(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let compressedPath = args["compressedPath"] as? String + else { + result(FlutterError(code: "bad_args", message: "缺少 compressedPath", details: nil)) + return + } + guard pendingShareResult == nil else { + result(FlutterError(code: "busy", message: "正在分享", details: nil)) + return + } + guard let controller = controller else { + result(FlutterError(code: "share_unavailable", message: "无法打开分享面板", details: nil)) + return + } + + do { + let sourceURL = URL(fileURLWithPath: compressedPath) + let suggestedName = args["suggestedName"] as? String ?? sourceURL.lastPathComponent + let exportURL = try makeExportCopy(sourceURL: sourceURL, suggestedName: suggestedName) + + let activityController = UIActivityViewController( + activityItems: [exportURL], + applicationActivities: nil + ) + if let popover = activityController.popoverPresentationController, + let view = controller.view + { + popover.sourceView = view + popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0) + popover.permittedArrowDirections = [] + } + pendingShareResult = result + activityController.completionWithItemsHandler = { [weak self] _, completed, _, error in + guard let self = self else { + return + } + let pendingResult = self.pendingShareResult + self.pendingShareResult = nil + if let error = error { + pendingResult?( + FlutterError(code: "share_failed", message: error.localizedDescription, details: nil) + ) + } else { + pendingResult?(["shared": completed]) + } + } + controller.present(activityController, animated: true) + } catch { + result( + FlutterError(code: "share_failed", message: error.localizedDescription, details: nil) + ) + } + } + + private func saveToGallery(arguments: Any?, result: @escaping FlutterResult) { + guard + let args = arguments as? [String: Any], + let compressedPath = args["compressedPath"] as? String + else { + result(FlutterError(code: "bad_args", message: "缺少 compressedPath", details: nil)) + return + } + + let sourceURL = URL(fileURLWithPath: compressedPath) + saveImageToPhotoLibrary(sourceURL: sourceURL, result: result) + } + + private func saveImageToPhotoLibrary( + sourceURL: URL, + result: @escaping FlutterResult + ) { + let saveChanges = { + PHPhotoLibrary.shared().performChanges({ + _ = PHAssetCreationRequest.creationRequestForAssetFromImage(atFileURL: sourceURL) + }) { success, error in + DispatchQueue.main.async { + if let error = error { + result(FlutterError(code: "save_failed", message: error.localizedDescription, details: nil)) + } else if success { + result(["saved": true]) + } else { + result(FlutterError(code: "save_failed", message: "保存到相册失败", details: nil)) + } + } + } + } + + if #available(iOS 14.0, *) { + switch PHPhotoLibrary.authorizationStatus(for: .addOnly) { + case .authorized, .limited: + saveChanges() + case .notDetermined: + PHPhotoLibrary.requestAuthorization(for: .addOnly) { status in + if status == .authorized || status == .limited { + saveChanges() + } else { + DispatchQueue.main.async { + result(FlutterError(code: "photo_denied", message: "没有相册保存权限", details: nil)) + } + } + } + default: + result(FlutterError(code: "photo_denied", message: "没有相册保存权限", details: nil)) + } + } else { + switch PHPhotoLibrary.authorizationStatus() { + case .authorized: + saveChanges() + case .notDetermined: + PHPhotoLibrary.requestAuthorization { status in + if status == .authorized { + saveChanges() + } else { + DispatchQueue.main.async { + result(FlutterError(code: "photo_denied", message: "没有相册保存权限", details: nil)) + } + } + } + default: + result(FlutterError(code: "photo_denied", message: "没有相册保存权限", details: nil)) + } + } + } + + func documentPicker( + _ controller: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL] + ) { + guard let operation = pendingOperation else { + return + } + pendingOperation = nil + + switch operation { + case .pick(let result): + do { + if let url = urls.first { + result([try buildPickedImage(sourceURL: url)]) + } else { + result([]) + } + } catch { + result( + FlutterError(code: "pick_failed", message: error.localizedDescription, details: nil) + ) + } + } + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + guard let operation = pendingOperation else { + return + } + pendingOperation = nil + + switch operation { + case .pick(let result): + result([]) + } + } + + private func buildPickedImage(sourceURL: URL) throws -> [String: Any] { + let scoped = sourceURL.startAccessingSecurityScopedResource() + defer { + if scoped { + sourceURL.stopAccessingSecurityScopedResource() + } + } + + let id = UUID().uuidString + sources[id] = sourceURL + + let previewURL = try copyToCache( + sourceURL: sourceURL, + child: "originals", + preferredName: sourceURL.lastPathComponent + ) + let dimensions = imageDimensions(for: sourceURL) ?? imageDimensions(for: previewURL) + let size = fileSize(sourceURL) ?? fileSize(previewURL) ?? 0 + + return [ + "sourceHandle": id, + "displayName": sourceURL.lastPathComponent.isEmpty + ? "image" + : sourceURL.lastPathComponent, + "previewPath": previewURL.path, + "originalSize": size, + "width": dimensions?.width ?? 0, + "height": dimensions?.height ?? 0, + "canOverwrite": FileManager.default.isWritableFile(atPath: sourceURL.path), + ] + } + + private func buildSharedImage(sourceURL: URL) throws -> [String: Any] { + let scoped = sourceURL.startAccessingSecurityScopedResource() + defer { + if scoped { + sourceURL.stopAccessingSecurityScopedResource() + } + } + + let displayName = sourceURL.lastPathComponent.isEmpty + ? "image" + : sourceURL.lastPathComponent + let previewURL = try copyToCache( + sourceURL: sourceURL, + child: "shared-originals", + preferredName: displayName + ) + let id = UUID().uuidString + sources[id] = previewURL + let dimensions = imageDimensions(for: previewURL) + let size = fileSize(previewURL) ?? 0 + + return [ + "sourceHandle": id, + "displayName": displayName, + "previewPath": previewURL.path, + "originalSize": size, + "width": dimensions?.width ?? 0, + "height": dimensions?.height ?? 0, + "canOverwrite": false, + ] + } + + private func compress(sourceURL: URL) throws -> [String: Any] { + let scoped = sourceURL.startAccessingSecurityScopedResource() + defer { + if scoped { + sourceURL.stopAccessingSecurityScopedResource() + } + } + + let originalData = try Data(contentsOf: sourceURL) + guard let image = UIImage(data: originalData) else { + throw NativeImageError.message("无法解码图片") + } + + let originalDimensions = imageDimensions(for: sourceURL) + ?? PixelSize( + width: max(1, Int(image.size.width * image.scale)), + height: max(1, Int(image.size.height * image.scale)) + ) + let targetDimensions = Self.targetSize(for: originalDimensions) + let hasAlpha = Self.hasAlpha(image) + let rendered = Self.render(image: image, target: targetDimensions, opaque: !hasAlpha) + let preservePng = hasAlpha && originalData.count <= 1_500_000 + let quality = Self.jpegQuality(for: targetDimensions) + + let encodedData = preservePng + ? rendered.pngData() + : rendered.jpegData(compressionQuality: quality) + guard let encoded = encodedData else { + throw NativeImageError.message("无法生成压缩图片") + } + + var outputData = encoded + var outputDimensions = targetDimensions + var outputExtension = preservePng ? "png" : "jpg" + var passthrough = false + + if encoded.count >= originalData.count { + outputData = originalData + outputDimensions = originalDimensions + outputExtension = sourceURL.pathExtension.isEmpty ? outputExtension : sourceURL.pathExtension + passthrough = true + } + + let outputURL = try makeCompressedURL( + originalName: sourceURL.deletingPathExtension().lastPathComponent, + extensionName: outputExtension + ) + try outputData.write(to: outputURL, options: .atomic) + + return [ + "path": outputURL.path, + "outputSize": outputData.count, + "width": outputDimensions.width, + "height": outputDimensions.height, + "passthrough": passthrough, + ] + } + + private func copyToCache( + sourceURL: URL, + child: String, + preferredName: String + ) throws -> URL { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent( + child, + isDirectory: true + ) + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true + ) + let targetURL = directory.appendingPathComponent( + "\(UUID().uuidString)-\(Self.sanitizeFileName(preferredName))" + ) + + do { + try FileManager.default.copyItem(at: sourceURL, to: targetURL) + } catch { + let data = try Data(contentsOf: sourceURL) + try data.write(to: targetURL, options: .atomic) + } + return targetURL + } + + private func makeCompressedURL(originalName: String, extensionName: String) throws -> URL { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent( + "compressed", + isDirectory: true + ) + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true + ) + let safeName = Self.sanitizeFileName(originalName.isEmpty ? "image" : originalName) + return directory.appendingPathComponent("\(UUID().uuidString)-\(safeName).\(extensionName)") + } + + private func makeExportCopy(sourceURL: URL, suggestedName: String) throws -> URL { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent( + "exports", + isDirectory: true + ) + try FileManager.default.createDirectory( + at: directory, + withIntermediateDirectories: true + ) + let targetURL = directory.appendingPathComponent(Self.sanitizeFileName(suggestedName)) + if FileManager.default.fileExists(atPath: targetURL.path) { + try FileManager.default.removeItem(at: targetURL) + } + try FileManager.default.copyItem(at: sourceURL, to: targetURL) + return targetURL + } + + private func imageDimensions(for url: URL) -> PixelSize? { + guard + let source = CGImageSourceCreateWithURL(url as CFURL, [ + kCGImageSourceShouldCache: false + ] as CFDictionary), + let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any], + let width = properties[kCGImagePropertyPixelWidth] as? NSNumber, + let height = properties[kCGImagePropertyPixelHeight] as? NSNumber + else { + return nil + } + return PixelSize(width: width.intValue, height: height.intValue) + } + + private func fileSize(_ url: URL) -> Int? { + if let values = try? url.resourceValues(forKeys: [.fileSizeKey]), + let size = values.fileSize + { + return size + } + if let attrs = try? FileManager.default.attributesOfItem(atPath: url.path), + let size = attrs[.size] as? NSNumber + { + return size.intValue + } + return nil + } + + private static func targetSize(for source: PixelSize) -> PixelSize { + let width = max(1, source.width) + let height = max(1, source.height) + let longSide = max(width, height) + let shortSide = min(width, height) + let aspect = Double(shortSide) / Double(longSide) + var scale = 1.0 + + if longSide > 10_800 && aspect > 0.25 { + scale = min(scale, 1440.0 / Double(longSide)) + } else if shortSide > 1440 { + scale = min(scale, 1440.0 / Double(shortSide)) + } + + let sourcePixels = Double(width) * Double(height) + if sourcePixels > 40_960_000 { + scale = min(scale, 0.5) + } + + let scaledPixels = sourcePixels * scale * scale + if scaledPixels > 10_240_000 { + scale = min(scale, sqrt(10_240_000 / sourcePixels)) + } + + return PixelSize( + width: max(1, Int((Double(width) * scale).rounded(.down))), + height: max(1, Int((Double(height) * scale).rounded(.down))) + ) + } + + private static func jpegQuality(for size: PixelSize) -> CGFloat { + let megapixels = Double(size.width * size.height) / 1_000_000 + if megapixels < 0.5 { + return 0.92 + } + if megapixels < 1.0 { + return 0.88 + } + if megapixels < 3.0 { + return 0.8 + } + return 0.72 + } + + private static func render(image: UIImage, target: PixelSize, opaque: Bool) -> UIImage { + let size = CGSize(width: CGFloat(target.width), height: CGFloat(target.height)) + let format = UIGraphicsImageRendererFormat.default() + format.scale = 1 + format.opaque = opaque + return UIGraphicsImageRenderer(size: size, format: format).image { _ in + image.draw(in: CGRect(origin: .zero, size: size)) + } + } + + private static func hasAlpha(_ image: UIImage) -> Bool { + guard let alpha = image.cgImage?.alphaInfo else { + return false + } + switch alpha { + case .first, .last, .premultipliedFirst, .premultipliedLast: + return true + default: + return false + } + } + + private static func sanitizeFileName(_ name: String) -> String { + let allowed = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-") + let sanitized = name.unicodeScalars + .map { allowed.contains($0) ? String($0) : "_" } + .joined() + return sanitized.isEmpty ? "image.jpg" : sanitized + } +} + +private struct PixelSize { + let width: Int + let height: Int +} + +private enum NativeImageError: LocalizedError { + case message(String) + + var errorDescription: String? { + switch self { + case .message(let message): + return message + } + } +} diff --git a/ios/Runner/en.lproj/InfoPlist.strings b/ios/Runner/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..8585b55 --- /dev/null +++ b/ios/Runner/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +"CFBundleDisplayName" = "Luban Imager"; +"CFBundleName" = "Luban Imager"; diff --git a/ios/Runner/zh-Hans.lproj/InfoPlist.strings b/ios/Runner/zh-Hans.lproj/InfoPlist.strings new file mode 100644 index 0000000..5a08a67 --- /dev/null +++ b/ios/Runner/zh-Hans.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +"CFBundleDisplayName" = "鲁班压图"; +"CFBundleName" = "鲁班压图"; diff --git a/ios/Runner/zh-Hant.lproj/InfoPlist.strings b/ios/Runner/zh-Hant.lproj/InfoPlist.strings new file mode 100644 index 0000000..5a08a67 --- /dev/null +++ b/ios/Runner/zh-Hant.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +"CFBundleDisplayName" = "鲁班压图"; +"CFBundleName" = "鲁班压图"; diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..e3451de --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,1170 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() { + runApp(const LubanImagerApp()); +} + +class LubanImagerApp extends StatefulWidget { + const LubanImagerApp({super.key}); + + @override + State createState() => _LubanImagerAppState(); +} + +class _LubanImagerAppState extends State + with WidgetsBindingObserver { + late String _appName; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _appName = AppName.forLocales( + WidgetsBinding.instance.platformDispatcher.locales, + ); + } + + @override + void didChangeLocales(List? locales) { + setState(() { + _appName = AppName.forLocales( + locales ?? WidgetsBinding.instance.platformDispatcher.locales, + ); + }); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: _appName, + theme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF2F6F73), + brightness: Brightness.light, + ), + scaffoldBackgroundColor: const Color(0xFFF7F8F6), + appBarTheme: const AppBarTheme( + centerTitle: false, + backgroundColor: Color(0xFFF7F8F6), + foregroundColor: Color(0xFF172122), + elevation: 0, + ), + ), + home: ImagerHomePage(appName: _appName), + ); + } +} + +class AppName { + static const english = 'Luban Imager'; + static const chinese = '鲁班压图'; + + static String forLocales(List locales) { + final locale = locales.isNotEmpty + ? locales.first + : WidgetsBinding.instance.platformDispatcher.locale; + return forLocale(locale); + } + + static String forLocale(Locale locale) { + return locale.languageCode.toLowerCase() == 'zh' ? chinese : english; + } +} + +class ImagerHomePage extends StatefulWidget { + const ImagerHomePage({required this.appName, super.key}); + + final String appName; + + @override + State createState() => _ImagerHomePageState(); +} + +class _ImagerHomePageState extends State { + static const MethodChannel _channel = MethodChannel( + 'luban_imager/native_images', + ); + + ImageJob? _job; + var _previewMode = PreviewMode.original; + var _compareFraction = 0.5; + var _isPicking = false; + OverlayEntry? _toastEntry; + Timer? _toastTimer; + + @override + void initState() { + super.initState(); + _channel.setMethodCallHandler(_handleNativeCall); + WidgetsBinding.instance.addPostFrameCallback((_) { + _takeSharedImages(); + }); + } + + @override + void dispose() { + _channel.setMethodCallHandler(null); + _removeToast(); + super.dispose(); + } + + Future _handleNativeCall(MethodCall call) async { + switch (call.method) { + case 'sharedImages': + await _handleNativeImages(call.arguments); + return null; + default: + throw MissingPluginException('No handler for ${call.method}'); + } + } + + Future _takeSharedImages() async { + try { + final response = await _channel.invokeMethod>( + 'takeSharedImages', + ); + await _handleNativeImages(response); + } on MissingPluginException { + // Widget tests and unsupported platforms do not provide the native bridge. + } on PlatformException catch (error) { + _showMessage(error.message ?? '读取分享图片失败'); + } + } + + Future _handleNativeImages(Object? response) async { + final images = response is List ? response : const []; + if (!mounted || images.isEmpty) { + return; + } + + final first = images.first; + if (first is! Map) { + return; + } + + await _startImageJob( + NativeImage.fromMap(Map.from(first)), + message: '已接收分享图片,正在压缩', + ); + } + + Future _startImageJob(NativeImage image, {String? message}) async { + if (!mounted) { + return; + } + + setState(() { + _job = ImageJob(image); + _previewMode = PreviewMode.original; + _compareFraction = 0.5; + }); + if (message != null) { + _showMessage(message); + } + await _compressSelected(); + } + + Future _pickImages() async { + if (_isPicking) { + return; + } + + NativeImage? picked; + setState(() { + _isPicking = true; + }); + + try { + final response = await _channel.invokeMethod>('pickImages'); + final images = response ?? []; + + if (!mounted || images.isEmpty) { + return; + } + + picked = NativeImage.fromMap( + Map.from(images.first as Map), + ); + } on PlatformException catch (error) { + _showMessage(error.message ?? '选择图片失败'); + } finally { + if (mounted) { + setState(() { + _isPicking = false; + }); + } + } + + if (picked != null && mounted) { + await _startImageJob(picked); + } + } + + Future _compressSelected() async { + final job = _job; + if (job == null) { + return; + } + if (job.isCompressing) { + return; + } + + setState(() { + job + ..isCompressing = true + ..error = null; + }); + + try { + final response = await _channel + .invokeMethod>('compressImage', { + 'sourceHandle': job.original.sourceHandle, + 'displayName': job.original.displayName, + 'previewPath': job.original.previewPath, + 'originalSize': job.original.originalSize, + 'originalWidth': job.original.width, + 'originalHeight': job.original.height, + }); + if (response == null) { + throw const FormatException('native compressor returned no data'); + } + final compressed = CompressedImage.fromMap(response); + + if (!mounted) { + return; + } + setState(() { + job + ..compressed = compressed + ..isCompressing = false + ..overwritten = false; + _previewMode = PreviewMode.compare; + }); + _showMessage( + compressed.passthrough + ? '压缩后比原图更大,已使用原图' + : '压缩完成,节省 ${_savingLabel(job)}', + ); + } on Object catch (error) { + if (!mounted) { + return; + } + setState(() { + job + ..isCompressing = false + ..error = _errorText(error); + }); + _showMessage(job.error ?? '压缩失败'); + } + } + + Future _saveSelected() async { + final job = _job; + final compressed = job?.compressed; + if (job == null || compressed == null) { + return; + } + + try { + await _channel.invokeMethod>('saveToGallery', { + 'compressedPath': compressed.path, + 'suggestedName': _suggestedName(job.original.displayName), + }); + _showMessage('已保存到相册'); + } on PlatformException catch (error) { + _showMessage(error.message ?? '保存失败'); + } + } + + Future _shareSelected() async { + await _openShareSheet(); + } + + Future _openShareSheet() async { + final job = _job; + final compressed = job?.compressed; + if (job == null || compressed == null) { + return; + } + + try { + await _channel.invokeMethod>('shareImage', { + 'compressedPath': compressed.path, + 'suggestedName': _suggestedName(job.original.displayName), + }); + } on PlatformException catch (error) { + _showMessage(error.message ?? '分享失败'); + } + } + + Future _overwriteSelected() async { + final job = _job; + final compressed = job?.compressed; + if (job == null || compressed == null) { + return; + } + if (!job.original.canOverwrite) { + _showMessage('当前来源不支持原位覆盖,请使用保存'); + return; + } + + final confirmed = await _confirmOverwrite(job.original.displayName); + if (!confirmed || !mounted) { + return; + } + + try { + await _channel.invokeMethod>('overwriteOriginal', { + 'sourceHandle': job.original.sourceHandle, + 'compressedPath': compressed.path, + }); + if (!mounted) { + return; + } + setState(() { + job.overwritten = true; + }); + _showMessage('已覆盖'); + } on PlatformException catch (error) { + _showMessage(error.message ?? '覆盖失败'); + } + } + + Future _confirmOverwrite(String name) async { + final first = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('覆盖'), + content: Text('将用压缩后的图片替换「$name」,此操作无法从 App 内撤销。'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('取消'), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('继续'), + ), + ], + ), + ); + if (first != true || !mounted) { + return false; + } + + final second = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('再次确认'), + content: const Text('确认覆盖?建议先保存备份。'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('取消'), + ), + FilledButton( + style: FilledButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error, + foregroundColor: Theme.of(context).colorScheme.onError, + ), + onPressed: () => Navigator.of(context).pop(true), + child: const Text('覆盖'), + ), + ], + ), + ); + return second == true; + } + + void _clearImage() { + setState(() { + _job = null; + _previewMode = PreviewMode.original; + _compareFraction = 0.5; + }); + } + + void _showMessage(String message) { + if (!mounted) { + return; + } + + _removeToast(); + + final overlay = Overlay.maybeOf(context, rootOverlay: true); + if (overlay == null) { + return; + } + + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onInverseSurface, + fontWeight: FontWeight.w700, + ); + + _toastEntry = OverlayEntry( + builder: (context) { + final top = MediaQuery.of(context).padding.top + kToolbarHeight + 8; + return Positioned( + left: 16, + right: 16, + top: top, + child: IgnorePointer( + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 560), + child: Material( + color: colorScheme.inverseSurface, + elevation: 8, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + child: Text( + message, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: textStyle, + ), + ), + ), + ), + ), + ), + ); + }, + ); + overlay.insert(_toastEntry!); + _toastTimer = Timer(const Duration(seconds: 1), () { + _toastEntry?.remove(); + _toastEntry = null; + _toastTimer = null; + }); + } + + void _removeToast() { + _toastTimer?.cancel(); + _toastTimer = null; + _toastEntry?.remove(); + _toastEntry = null; + } + + @override + Widget build(BuildContext context) { + final job = _job; + + return Scaffold( + appBar: AppBar( + title: Text(widget.appName), + actions: job == null + ? null + : [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: TextButton.icon( + onPressed: _isPicking ? null : _pickImages, + icon: const Icon(Icons.add_photo_alternate_outlined), + label: const Text('重新选择'), + ), + ), + ], + ), + body: SafeArea( + child: job == null ? _buildEmptyState() : _buildPreviewPanel(job), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.image_search_outlined, + size: 72, + color: Color(0xFF5D6F70), + ), + const SizedBox(height: 20), + Text( + '选择图片开始压缩', + style: Theme.of( + context, + ).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w700), + ), + const SizedBox(height: 16), + FilledButton.icon( + onPressed: _isPicking ? null : _pickImages, + icon: const Icon(Icons.add_photo_alternate_outlined), + label: const Text('选择图片'), + ), + ], + ), + ), + ); + } + + Widget _buildPreviewPanel(ImageJob job) { + final compressed = job.compressed; + final canCompare = compressed != null; + final mode = canCompare ? _previewMode : PreviewMode.original; + + return LayoutBuilder( + builder: (context, constraints) { + final showCompareSlider = mode == PreviewMode.compare && canCompare; + final showWarning = compressed?.passthrough == true; + const minPreviewHeight = 200.0; + const baseChromeHeight = 360.0; + const compareSliderHeight = 52.0; + const warningHeight = 58.0; + // Keep the panel fixed-height while the preview can stay at least 200px. + final chromeHeight = + baseChromeHeight + + (showWarning ? warningHeight : 0.0) - + (showCompareSlider ? 0.0 : compareSliderHeight); + final availablePreviewHeight = constraints.maxHeight - chromeHeight; + final previewHeight = math.max( + minPreviewHeight, + availablePreviewHeight, + ); + + List buildContent() { + return [ + SizedBox( + height: 40, + child: Row( + children: [ + Expanded( + child: Text( + job.original.displayName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w800, + ), + ), + ), + IconButton( + tooltip: '移除', + visualDensity: VisualDensity.compact, + onPressed: _clearImage, + icon: const Icon(Icons.close), + ), + ], + ), + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: _MetricBlock( + label: '原图', + value: _formatSize(job.original.originalSize), + detail: '${job.original.width}×${job.original.height}', + ), + ), + const SizedBox(width: 10), + Expanded( + child: _MetricBlock( + label: '压缩后', + value: compressed == null + ? '--' + : _formatSize(compressed.outputSize), + detail: compressed == null + ? '--' + : '${compressed.width}×${compressed.height}', + ), + ), + const SizedBox(width: 10), + Expanded( + child: _MetricBlock( + label: '节省', + value: compressed == null ? '--' : _savingLabel(job), + detail: compressed?.passthrough == true ? '压缩后更大' : '', + ), + ), + ], + ), + if (showWarning) ...[ + const SizedBox(height: 10), + const _WarningStrip(message: '压缩结果比原图更大,已自动使用原图。'), + ], + const SizedBox(height: 14), + SegmentedButton( + showSelectedIcon: false, + segments: const [ + ButtonSegment( + value: PreviewMode.original, + icon: Icon(Icons.image_outlined), + label: Text('原图'), + ), + ButtonSegment( + value: PreviewMode.compressed, + icon: Icon(Icons.photo_size_select_large_outlined), + label: Text('压缩'), + ), + ButtonSegment( + value: PreviewMode.compare, + icon: Icon(Icons.compare_outlined), + label: Text('对比'), + ), + ], + selected: {mode}, + onSelectionChanged: canCompare + ? (selection) { + setState(() { + _previewMode = selection.first; + }); + } + : null, + ), + const SizedBox(height: 14), + SizedBox( + height: previewHeight, + child: DecoratedBox( + decoration: BoxDecoration( + color: const Color(0xFFEDEFEA), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFDADFD8)), + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: _buildImagePreview(job, mode), + ), + ), + ), + if (showCompareSlider) ...[ + const SizedBox(height: 4), + Slider( + value: _compareFraction, + min: 0.05, + max: 0.95, + onChanged: (value) { + setState(() { + _compareFraction = value; + }); + }, + ), + ], + const SizedBox(height: 10), + Wrap( + spacing: 10, + runSpacing: 10, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + FilledButton.icon( + onPressed: compressed == null || job.isCompressing + ? null + : _saveSelected, + icon: const Icon(Icons.save_alt_outlined), + label: const Text('保存'), + ), + FilledButton.tonalIcon( + onPressed: compressed == null || job.isCompressing + ? null + : _shareSelected, + icon: const Icon(Icons.ios_share_outlined), + label: const Text('分享'), + ), + FilledButton.icon( + style: FilledButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.error, + foregroundColor: Theme.of(context).colorScheme.onError, + ), + onPressed: compressed == null ? null : _overwriteSelected, + icon: const Icon(Icons.warning_amber_rounded), + label: const Text('覆盖'), + ), + ], + ), + ]; + } + + final child = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: buildContent(), + ); + if (availablePreviewHeight >= minPreviewHeight) { + return Padding(padding: const EdgeInsets.all(16), child: child); + } + + return Scrollbar( + child: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: child, + ), + ); + }, + ); + } + + Widget _buildImagePreview(ImageJob job, PreviewMode mode) { + final compressedPath = job.compressed?.path; + if (mode == PreviewMode.compressed && compressedPath != null) { + return _ZoomablePreview( + width: job.compressed?.width ?? job.original.width, + height: job.compressed?.height ?? job.original.height, + child: _PreviewImage(path: compressedPath), + ); + } + if (mode == PreviewMode.compare && compressedPath != null) { + return _ZoomablePreview( + width: job.original.width, + height: job.original.height, + overlay: const Stack( + children: [ + Positioned(left: 12, top: 12, child: _PreviewBadge(label: '原图')), + Positioned(right: 12, top: 12, child: _PreviewBadge(label: '压缩')), + ], + ), + child: LayoutBuilder( + builder: (context, constraints) { + return Stack( + fit: StackFit.expand, + children: [ + _PreviewImage(path: compressedPath), + ClipRect( + clipper: _CompareClipper(_compareFraction), + child: _PreviewImage(path: job.original.previewPath), + ), + Positioned( + left: constraints.maxWidth * _compareFraction - 1, + top: 0, + bottom: 0, + child: Container(width: 2, color: Colors.white), + ), + ], + ); + }, + ), + ); + } + return _ZoomablePreview( + width: job.original.width, + height: job.original.height, + child: _PreviewImage(path: job.original.previewPath), + ); + } + + String _savingLabel(ImageJob job) { + final compressed = job.compressed; + if (compressed == null || job.original.originalSize <= 0) { + return '--'; + } + if (compressed.passthrough) { + return '使用原图'; + } + final saved = job.original.originalSize - compressed.outputSize; + if (saved <= 0) { + return '0%'; + } + return '${(saved / job.original.originalSize * 100).toStringAsFixed(1)}%'; + } + + String _suggestedName(String originalName) { + final dot = originalName.lastIndexOf('.'); + if (dot <= 0) { + return '${originalName}_luban.jpg'; + } + return '${originalName.substring(0, dot)}_luban${originalName.substring(dot)}'; + } + + String _formatSize(int bytes) { + if (bytes <= 0) { + return '0 B'; + } + const units = ['B', 'KB', 'MB', 'GB']; + var value = bytes.toDouble(); + var unit = 0; + while (value >= 1024 && unit < units.length - 1) { + value /= 1024; + unit++; + } + final digits = unit == 0 ? 0 : 1; + return '${value.toStringAsFixed(digits)} ${units[unit]}'; + } + + String _errorText(Object error) { + if (error is PlatformException) { + return error.message ?? error.code; + } + return error.toString(); + } +} + +enum PreviewMode { original, compressed, compare } + +class NativeImage { + NativeImage({ + required this.sourceHandle, + required this.displayName, + required this.previewPath, + required this.originalSize, + required this.width, + required this.height, + required this.canOverwrite, + }); + + factory NativeImage.fromMap(Map map) { + return NativeImage( + sourceHandle: map['sourceHandle'] as String, + displayName: map['displayName'] as String? ?? 'image', + previewPath: map['previewPath'] as String, + originalSize: (map['originalSize'] as num?)?.toInt() ?? 0, + width: (map['width'] as num?)?.toInt() ?? 0, + height: (map['height'] as num?)?.toInt() ?? 0, + canOverwrite: map['canOverwrite'] as bool? ?? false, + ); + } + + final String sourceHandle; + final String displayName; + final String previewPath; + final int originalSize; + final int width; + final int height; + final bool canOverwrite; +} + +class CompressedImage { + CompressedImage({ + required this.path, + required this.outputSize, + required this.width, + required this.height, + required this.passthrough, + }); + + factory CompressedImage.fromMap(Map map) { + return CompressedImage( + path: map['path'] as String, + outputSize: (map['outputSize'] as num?)?.toInt() ?? 0, + width: (map['width'] as num?)?.toInt() ?? 0, + height: (map['height'] as num?)?.toInt() ?? 0, + passthrough: map['passthrough'] as bool? ?? false, + ); + } + + final String path; + final int outputSize; + final int width; + final int height; + final bool passthrough; +} + +class ImageJob { + ImageJob(this.original); + + final NativeImage original; + CompressedImage? compressed; + bool isCompressing = false; + bool overwritten = false; + String? error; +} + +class _WarningStrip extends StatelessWidget { + const _WarningStrip({required this.message}); + + final String message; + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).colorScheme.error; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withValues(alpha: 0.28)), + ), + child: Row( + children: [ + Icon(Icons.warning_amber_rounded, size: 18, color: color), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: TextStyle(color: color, fontWeight: FontWeight.w700), + ), + ), + ], + ), + ); + } +} + +class _MetricBlock extends StatelessWidget { + const _MetricBlock({ + required this.label, + required this.value, + required this.detail, + }); + + final String label; + final String value; + final String detail; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFDADFD8)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.labelMedium?.copyWith(color: const Color(0xFF5D6F70)), + ), + const SizedBox(height: 7), + Text( + value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of( + context, + ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800), + ), + const SizedBox(height: 2), + Text( + detail, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ); + } +} + +class _ZoomablePreview extends StatefulWidget { + const _ZoomablePreview({ + required this.width, + required this.height, + required this.child, + this.overlay, + }); + + final int width; + final int height; + final Widget child; + final Widget? overlay; + + @override + State<_ZoomablePreview> createState() => _ZoomablePreviewState(); +} + +class _ZoomablePreviewState extends State<_ZoomablePreview> { + static const _minScale = 1.0; + static const _maxScale = 8.0; + + Size _viewportSize = Size.zero; + Size _canvasSize = Size.zero; + var _scale = _minScale; + var _offset = Offset.zero; + var _gestureStartScale = _minScale; + var _gestureStartOffset = Offset.zero; + var _gestureStartScenePoint = Offset.zero; + + @override + void didUpdateWidget(covariant _ZoomablePreview oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.width != widget.width || oldWidget.height != widget.height) { + _resetTransform(); + } + } + + void _resetTransform() { + _scale = _minScale; + _offset = Offset.zero; + } + + double _clampScale(double scale) { + return scale.clamp(_minScale, _maxScale).toDouble(); + } + + Offset _clampOffset(Offset offset, double scale) { + final maxX = math.max( + 0.0, + (_canvasSize.width * scale - _viewportSize.width) / 2, + ); + final maxY = math.max( + 0.0, + (_canvasSize.height * scale - _viewportSize.height) / 2, + ); + + return Offset( + maxX == 0 ? 0 : offset.dx.clamp(-maxX, maxX).toDouble(), + maxY == 0 ? 0 : offset.dy.clamp(-maxY, maxY).toDouble(), + ); + } + + void _startGesture(ScaleStartDetails details) { + _gestureStartScale = _scale; + _gestureStartOffset = _offset; + final center = _viewportSize.center(Offset.zero); + _gestureStartScenePoint = + (details.localFocalPoint - center - _gestureStartOffset) / _scale; + } + + void _updateGesture(ScaleUpdateDetails details) { + final nextScale = _clampScale(_gestureStartScale * details.scale); + final center = _viewportSize.center(Offset.zero); + final nextOffset = + details.localFocalPoint - center - _gestureStartScenePoint * nextScale; + + setState(() { + _scale = nextScale; + _offset = _clampOffset(nextOffset, nextScale); + }); + } + + void _endGesture(ScaleEndDetails details) { + setState(() { + _scale = _clampScale(_scale); + _offset = _clampOffset(_offset, _scale); + }); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + _viewportSize = Size(constraints.maxWidth, constraints.maxHeight); + + final imageWidth = math.max(1, widget.width).toDouble(); + final imageHeight = math.max(1, widget.height).toDouble(); + final aspectRatio = imageWidth / imageHeight; + var canvasWidth = constraints.maxWidth; + var canvasHeight = canvasWidth / aspectRatio; + + if (canvasHeight > constraints.maxHeight) { + canvasHeight = constraints.maxHeight; + canvasWidth = canvasHeight * aspectRatio; + } + _canvasSize = Size(canvasWidth, canvasHeight); + _scale = _clampScale(_scale); + _offset = _clampOffset(_offset, _scale); + + return ClipRect( + child: Stack( + fit: StackFit.expand, + children: [ + GestureDetector( + behavior: HitTestBehavior.opaque, + onScaleStart: _startGesture, + onScaleUpdate: _updateGesture, + onScaleEnd: _endGesture, + child: SizedBox.expand( + child: Center( + child: Transform.translate( + offset: _offset, + child: Transform.scale( + scale: _scale, + child: SizedBox( + width: canvasWidth, + height: canvasHeight, + child: widget.child, + ), + ), + ), + ), + ), + ), + if (widget.overlay != null) + Positioned.fill(child: IgnorePointer(child: widget.overlay)), + ], + ), + ); + }, + ); + } +} + +class _PreviewImage extends StatelessWidget { + const _PreviewImage({required this.path}); + + final String path; + + @override + Widget build(BuildContext context) { + return Image.file( + File(path), + fit: BoxFit.contain, + gaplessPlayback: true, + errorBuilder: (context, error, stackTrace) { + return const Center(child: Icon(Icons.broken_image_outlined, size: 48)); + }, + ); + } +} + +class _PreviewBadge extends StatelessWidget { + const _PreviewBadge({required this.label}); + + final String label; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.62), + borderRadius: BorderRadius.circular(99), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: Text( + label, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + ), + ); + } +} + +class _CompareClipper extends CustomClipper { + const _CompareClipper(this.fraction); + + final double fraction; + + @override + Rect getClip(Size size) { + return Rect.fromLTWH(0, 0, size.width * fraction, size.height); + } + + @override + bool shouldReclip(_CompareClipper oldClipper) { + return oldClipper.fraction != fraction; + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..4173763 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,213 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" +sdks: + dart: ">=3.11.5 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..bc6cd56 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,89 @@ +name: luban_imager +description: "A Flutter image compression app powered by Luban-style smart compression." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.5 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..9ab86a4 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,26 @@ +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:luban_imager/main.dart'; + +void main() { + testWidgets('uses English app name by default', (tester) async { + await tester.pumpWidget(const LubanImagerApp()); + + expect(find.text(AppName.english), findsOneWidget); + expect(find.text('选择图片'), findsWidgets); + }); + + testWidgets('uses Chinese app name for Chinese locale', (tester) async { + tester.binding.platformDispatcher.localesTestValue = const [ + Locale('zh', 'CN'), + ]; + addTearDown(tester.binding.platformDispatcher.clearLocalesTestValue); + + await tester.pumpWidget(const LubanImagerApp()); + + expect(find.text(AppName.chinese), findsOneWidget); + expect(find.text(AppName.english), findsNothing); + expect(find.text('选择图片'), findsWidgets); + }); +}