├── devtools_options.yaml
├── metadata
├── en-US
│ ├── changelogs
│ │ ├── 9.txt
│ │ ├── 6.txt
│ │ ├── 7.txt
│ │ ├── 2.txt
│ │ ├── 3.txt
│ │ ├── 1.txt
│ │ ├── 5.txt
│ │ ├── 10.txt
│ │ ├── 4.txt
│ │ └── 8.txt
│ ├── short_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ └── 3.png
│ └── full_description.txt
└── zh-CN
│ ├── changelogs
│ ├── 6.txt
│ ├── 7.txt
│ ├── 9.txt
│ ├── 1.txt
│ ├── 2.txt
│ ├── 3.txt
│ ├── 5.txt
│ ├── 10.txt
│ ├── 4.txt
│ └── 8.txt
│ ├── short_description.txt
│ ├── full_description.txt
│ └── images
│ ├── icon.png
│ └── phoneScreenshots
│ ├── 1.png
│ ├── 2.png
│ └── 3.png
├── ios
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-50x50@1x.png
│ │ │ ├── Icon-App-50x50@2x.png
│ │ │ ├── Icon-App-57x57@1x.png
│ │ │ ├── Icon-App-57x57@2x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-72x72@1x.png
│ │ │ ├── Icon-App-72x72@2x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ └── Contents.json
│ ├── Runner.entitlements
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ └── Info.plist
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── RunnerTests
│ └── RunnerTests.swift
├── .gitignore
├── Podfile
└── Podfile.lock
├── assets
├── icons
│ ├── github_mark.png
│ ├── snapsaver_icon.png
│ └── snapsaver_icon.svg
└── sounds
│ └── camera_shutter.mp3
├── l10n.yaml
├── .gitmodules
├── android
├── gradle.properties
├── app
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ └── launcher_icon.png
│ │ │ │ ├── values-zh-rCN
│ │ │ │ │ └── strings.xml
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── drawable
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21
│ │ │ │ │ └── launch_background.xml
│ │ │ │ └── values-night
│ │ │ │ │ └── styles.xml
│ │ │ ├── kotlin
│ │ │ │ └── lying
│ │ │ │ │ └── fengfeng
│ │ │ │ │ └── snapsaver
│ │ │ │ │ └── snap_saver
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── build.gradle
└── settings.gradle
├── lib
├── file
│ ├── abs_path_picker.dart
│ └── android_native_path_picker.dart
├── dialog
│ ├── path_selector_entity.dart
│ ├── help_dialog.dart
│ ├── remove_saver_dialog.dart
│ ├── more_dialog.dart
│ └── insert_saver_dialog.dart
├── constants.dart
├── entity
│ ├── more.dart
│ └── saver.dart
├── viewmodel
│ ├── dialog_view_model.dart
│ └── home_view_model.dart
├── l10n
│ ├── app_zh.arb
│ └── app_en.arb
├── db
│ └── SaverDatabase.dart
├── main.dart
├── settings_screen.dart
└── home_screen.dart
├── .gitignore
├── test
└── widget_test.dart
├── README.zh.md
├── .metadata
├── analysis_options.yaml
├── .github
└── workflows
│ └── main.yml
├── README.md
├── pubspec.yaml
├── LICENSE
└── pubspec.lock
/devtools_options.yaml:
--------------------------------------------------------------------------------
1 | extensions:
2 |
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/9.txt:
--------------------------------------------------------------------------------
1 | bugfix
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/6.txt:
--------------------------------------------------------------------------------
1 | 实现了手动对焦
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/7.txt:
--------------------------------------------------------------------------------
1 | 实现滑动条变焦
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/9.txt:
--------------------------------------------------------------------------------
1 | bugfix
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/1.txt:
--------------------------------------------------------------------------------
1 | 发布第一个版本☝️
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/2.txt:
--------------------------------------------------------------------------------
1 | 现在可以切换镜头了
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/3.txt:
--------------------------------------------------------------------------------
1 | 添加一些振动效果
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/5.txt:
--------------------------------------------------------------------------------
1 | 支持设置拍照键拍出的照片名称
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/6.txt:
--------------------------------------------------------------------------------
1 | Implement manual focus
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/7.txt:
--------------------------------------------------------------------------------
1 | Implement slider zoom
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/2.txt:
--------------------------------------------------------------------------------
1 | Now you can switch the lens
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/3.txt:
--------------------------------------------------------------------------------
1 | Add some vibration effect
--------------------------------------------------------------------------------
/metadata/zh-CN/short_description.txt:
--------------------------------------------------------------------------------
1 | 一个可以帮你快速拍照并整理到指定目录中的应用
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/1.txt:
--------------------------------------------------------------------------------
1 | Release the first version ☝️
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/5.txt:
--------------------------------------------------------------------------------
1 | Be able to set photo name for Saver
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | 1. 固定照片分辨率为4:3
2 | 2. 调整变焦画框和切换前后摄像头的位置
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/4.txt:
--------------------------------------------------------------------------------
1 | 支持拍照键颜色选择🎨
2 | 支持删除拍照键
3 | 支持在一个拍照键中存储多个路径
--------------------------------------------------------------------------------
/metadata/zh-CN/changelogs/8.txt:
--------------------------------------------------------------------------------
1 | 新增调整预览窗位置功能
2 | 新增调整APP颜色主题功能
3 | 新增调整照片分辨率功能
--------------------------------------------------------------------------------
/metadata/zh-CN/full_description.txt:
--------------------------------------------------------------------------------
1 | 你可以通过这个应用创建拍照键,制定照片的存储目录;后续只需要按下这个拍照键,就可以拍下一张照片并且将它存储到事先设置好的存储目录
--------------------------------------------------------------------------------
/assets/icons/github_mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/assets/icons/github_mark.png
--------------------------------------------------------------------------------
/l10n.yaml:
--------------------------------------------------------------------------------
1 | arb-dir: lib/l10n
2 | template-arb-file: app_en.arb
3 | output-localization-file: app_localizations.dart
--------------------------------------------------------------------------------
/metadata/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | An app to quickly take photos and organize them into specified directories.
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule ".flutter"]
2 | path = .flutter
3 | url = https://github.com/flutter/flutter.git
4 |
5 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/assets/icons/snapsaver_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/assets/icons/snapsaver_icon.png
--------------------------------------------------------------------------------
/assets/sounds/camera_shutter.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/assets/sounds/camera_shutter.mp3
--------------------------------------------------------------------------------
/metadata/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/en-US/images/icon.png
--------------------------------------------------------------------------------
/metadata/zh-CN/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/zh-CN/images/icon.png
--------------------------------------------------------------------------------
/lib/file/abs_path_picker.dart:
--------------------------------------------------------------------------------
1 | abstract class PathPicker {
2 |
3 | void selectPath(void Function (String?) callback);
4 |
5 | }
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | 1. Fixed photo resolution to 4:3
2 | 2. Adjusted zoom frame and front/back camera switch positions
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/4.txt:
--------------------------------------------------------------------------------
1 | Support Saver color selection 🎨
2 | Support Saver deletion
3 | Support storing multiple paths in one Saver
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/metadata/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/metadata/zh-CN/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/zh-CN/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/metadata/zh-CN/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/zh-CN/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/metadata/zh-CN/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/metadata/zh-CN/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 快存相机
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | SnapSaver
4 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/lib/dialog/path_selector_entity.dart:
--------------------------------------------------------------------------------
1 | class PathSelectorEntity {
2 | bool isPathSelected;
3 | String? path;
4 |
5 | PathSelectorEntity({this.isPathSelected = false, this.path});
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NielsLee/SnapSaver/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/metadata/en-US/changelogs/8.txt:
--------------------------------------------------------------------------------
1 | Added the function of adjusting the preview window position
2 | Added the function of adjusting the APP color theme
3 | Added the function of adjusting the photo resolution
--------------------------------------------------------------------------------
/metadata/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | You can use this app to create a Saver button and set a storage directory for photos; later, just press the Saver button to take a photo and store it in the pre-set storage directory.
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/lying/fengfeng/snapsaver/snap_saver/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package lying.fengfeng.snapsaver.snap_saver
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/lib/constants.dart:
--------------------------------------------------------------------------------
1 | class Constants {
2 | static const String dbName = "savers_database.db";
3 | static const String saverTableName = "savers";
4 | static const String pathTableName = "saver_paths";
5 | static const int dbVersion = 3;
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/lib/entity/more.dart:
--------------------------------------------------------------------------------
1 | class More {
2 | final String? photoName;
3 | /**
4 | * 0: index
5 | * 1: timeStamp
6 | * 2: _index
7 | * 3: _timeStamp
8 | * 4: -index
9 | * 5: -timeStamp
10 | */
11 | final int suffixType;
12 |
13 | const More({this.photoName, this.suffixType = 0});
14 | }
15 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/lib/entity/saver.dart:
--------------------------------------------------------------------------------
1 | class Saver {
2 | final List paths;
3 | final String name;
4 | final int? color;
5 | int count;
6 | final String? photoName;
7 | final int suffixType;
8 |
9 | Saver(
10 | {required this.paths,
11 | required this.name,
12 | this.color,
13 | this.count = 0,
14 | this.photoName,
15 | this.suffixType = 0});
16 | }
17 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | 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.
--------------------------------------------------------------------------------
/lib/file/android_native_path_picker.dart:
--------------------------------------------------------------------------------
1 | import 'package:file_picker/file_picker.dart';
2 |
3 | import 'abs_path_picker.dart';
4 |
5 | class AndroidNativePathPicker extends PathPicker {
6 |
7 | @override
8 | void selectPath(void Function(String? p1) callback) async {
9 | String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
10 | callback(selectedDirectory);
11 | }
12 | }
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/dialog/help_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class HelpDialog extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | return AlertDialog(
8 | title: Text(AppLocalizations.of(context)!.howToUse),
9 | content: Text(AppLocalizations.of(context)!.helpContent),
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | maven { url 'https://storage.googleapis.com/download.flutter.io' }
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | rootProject.buildDir = '../build'
10 | subprojects {
11 | project.buildDir = "${rootProject.buildDir}/${project.name}"
12 | }
13 | subprojects {
14 | project.evaluationDependsOn(':app')
15 | }
16 |
17 | tasks.register("clean", Delete) {
18 | delete rootProject.buildDir
19 | }
20 |
21 | buildscript {
22 | ext.kotlin_version = '1.7.10'
23 | repositories {
24 | google()
25 | mavenCentral()
26 | }
27 |
28 | dependencies {
29 | classpath 'com.android.tools.build:gradle:7.3.0'
30 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Symbolication related
35 | app.*.symbols
36 |
37 | # Obfuscation related
38 | app.*.map.json
39 |
40 | # Android Studio will place build artifacts here
41 | /android/app/debug
42 | /android/app/profile
43 | /android/app/release
44 |
45 | *.jks
46 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
22 | id "com.android.application" version "8.7.0" apply false
23 | id "org.jetbrains.kotlin.android" version "1.9.10" apply false
24 | }
25 |
26 | include ":app"
27 |
--------------------------------------------------------------------------------
/lib/viewmodel/dialog_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | class DialogViewModel extends ChangeNotifier {
4 | String _name = "Saver Name";
5 | List _paths = [];
6 | Color? _color = null;
7 | String? _photoName;
8 | int _suffixType = 0;
9 |
10 | String getName() {
11 | return _name;
12 | }
13 |
14 | List getPath() {
15 | return _paths;
16 | }
17 |
18 | Color? getColor() {
19 | return _color;
20 | }
21 |
22 | String? getPhotoName() {
23 | return _photoName;
24 | }
25 |
26 | int getSuffixType() {
27 | return _suffixType;
28 | }
29 |
30 | void setName(name) {
31 | _name = name;
32 | }
33 |
34 | void addPath(path) {
35 | _paths.add(path);
36 | }
37 |
38 | void setColor(color) {
39 | _color = color;
40 | }
41 |
42 | void setPhotoName(name) {
43 | _photoName = name;
44 | }
45 |
46 | void setSuffixType(type) {
47 | _suffixType = type;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:snap_saver/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MainApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # 快存相机
2 |
3 | 一款相机应用程序,可以更轻松地将照片拍摄到选定的相册或目录。
4 |
5 |
10 |
11 | ## 入门指南
12 |
13 | [
](https://f-droid.org/packages/lying.fengfeng.snapsaver/)
16 | 或者从[发布记录](https://github.com/NielsLee/SnapSaver/releases/latest)下载最新版本.
17 |
18 | 1. 启动应用并授予相机权限。
19 | 2. 点击右下角的浮动按钮,在弹出的对话框中选择存储路径和拍照键的名称。
20 | 3. 输入信息后,点击确定,拍照键将保留在应用程序的主页上。
21 | 4. 按下刚创建的拍照键,相机预览界面上的照片将被拍摄并保存到 Saver 按钮保存的路径。
22 | 5. 您可以创建任意数量的拍照键,为它们分配不同的路径,然后可以从应用主页轻松拍摄照片并将结果保存到不同的路径!
23 |
24 | ## 注意事项
25 |
26 | **在很多安卓设备上,图片拍摄并存储到指定的目录中后,无法立刻被相册获取到。因此可能需要等待一段时间甚至重启以后才能在相册APP中看到刚刚拍摄的照片。作者凭兴趣开发这款软件,无法投入太多精力,因此暂时没有好的方案解决这个问题。如果正在浏览的各位有好的方案,可以通过Issue与作者交流。十分感谢!😊**
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "54e66469a933b60ddf175f858f82eaeb97e48c8d"
8 | channel: "stable"
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
17 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
18 | - platform: android
19 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
20 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
21 | - platform: ios
22 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
23 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/lib/l10n/app_zh.arb:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": "快存相机",
3 | "howToUse": "如何使用?",
4 | "helpContent": "首先,点击右下角的按钮,输入图片保存路径和名称,创建一个新的拍照键;然后,点击创建的按钮,即可拍照并保存到按钮对应的路径中。\n注意:在有的手机上,新建的文件夹需要重启以后才会被图库应用识别到。",
5 | "createANewSaver": "创建新的拍照键",
6 | "saverPath": "图片保存路径",
7 | "selectPath": "选择保存路径",
8 | "saverName": "拍照键名称",
9 | "saverNameDescription": "输入此拍照键的名称",
10 | "fileName": "文件名称",
11 | "ok": "确定",
12 | "cancel": "取消",
13 | "preview": "预览",
14 | "importAllExistingAlbums": "导入全部已有相册",
15 | "contactDeveloper": "联系开发者",
16 | "browseSourceCode": "浏览源码",
17 | "notYetImplemented": "尚未实现",
18 | "saverPathExisted": "❌已经存在这个路径的拍照键了",
19 | "thankForCharlie": "🎉感谢Charlie Sierra为本App提供的灵感!",
20 | "removeSaver": "删除拍照键?",
21 | "name": "名称",
22 | "path": "路径",
23 | "photoName": "照片名称",
24 | "photoNameDescription": "输入照片名称",
25 | "more": "更多",
26 | "photoIndex": "序号",
27 | "photoTimestamp": "时间",
28 | "photoNameExample": "照片名称示例",
29 | "moreDialogFinished": "照片名称设置完毕",
30 | "buy_me_coffee": "买杯咖啡给作者",
31 | "color_scheme": "颜色主题",
32 | "resolution_low": "低",
33 | "resolution_medium": "中",
34 | "resolution_high": "高",
35 | "resolution_vh": "超高",
36 | "resolution_uh": "极高",
37 | "resolution_max": "最大"
38 | }
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/l10n/app_en.arb:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": "SnapSaver",
3 | "howToUse": "How to use?",
4 | "helpContent": "First, click the button in the lower right corner, enter the path and name to save the photo, and create a photo shooting button; then, click the created button to take a photo and save it to the path corresponding to the button.\nNotice: In some devices, newly created path need a reboot to be recognized by album app.",
5 | "createANewSaver": "Create a new Saver",
6 | "saverPath": "Photo save path",
7 | "selectPath": "Select save path",
8 | "saverName": "Saver name",
9 | "saverNameDescription": "Input name of this saver",
10 | "fileName": "File name",
11 | "ok": "OK",
12 | "cancel": "Cancel",
13 | "preview": "Preview",
14 | "importAllExistingAlbums": "Import all existing albums",
15 | "contactDeveloper": "Contact author",
16 | "browseSourceCode": "Browse source code",
17 | "notYetImplemented": "Not yet implemented",
18 | "saverPathExisted": "❌Saver with this path already existed",
19 | "thankForCharlie": "🎉Thanks to Charlie Sierra for the inspiration for this app!",
20 | "removeSaver": "Remove Saver?",
21 | "name": "name",
22 | "path": "path",
23 | "photoName": "PhotoName",
24 | "photoNameDescription": "Input photo name",
25 | "more": "More",
26 | "photoIndex": "Index",
27 | "photoTimestamp": "Time",
28 | "photoNameExample": "PhotoName Example",
29 | "moreDialogFinished": "PhotoName set successfully",
30 | "buy_me_coffee": "Buy author a coffee",
31 | "color_scheme": "Color Scheme",
32 | "resolution_low": "low",
33 | "resolution_medium": "medium",
34 | "resolution_high": "high",
35 | "resolution_vh": "veryHigh",
36 | "resolution_uh": "ultraHigh",
37 | "resolution_max": "max"
38 | }
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Build Flutter APK
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build_apk:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/checkout@v4
15 | - name: set up JDK 17
16 | uses: actions/setup-java@v3
17 | with:
18 | java-version: '17'
19 | distribution: 'temurin'
20 | cache: gradle
21 |
22 | - name: Setup Flutter SDK
23 | uses: flutter-actions/setup-flutter@v3
24 | with:
25 | channel: stable
26 | version: 3.19.6
27 |
28 | - name: Install dependencies
29 | run: flutter pub get
30 |
31 | - name: Decode Keystore
32 | env:
33 | ENCODED_STRING: ${{ secrets.KEYSTORE_BASE_64 }}
34 | RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
35 | RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
36 | RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
37 |
38 | run: |
39 | echo $ENCODED_STRING > keystore-b64.txt
40 | base64 -d keystore-b64.txt > fengfengkeystore.jks
41 |
42 | - name: Build Release apk
43 | env:
44 | RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
45 | RELEASE_KEYSTORE_ALIAS: ${{ secrets.RELEASE_KEYSTORE_ALIAS }}
46 | RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
47 | run: flutter build apk --release
48 |
49 | - name: Get release file apk path
50 | id: releaseApk
51 | run: echo "apkfile=$(find build/app/outputs/flutter-apk/*.apk)" >> $GITHUB_OUTPUT
52 |
53 | - name: Upload Release Build to Artifacts
54 | uses: actions/upload-artifact@v4
55 | with:
56 | name: release-artifacts
57 | path: ${{ steps.releaseApk.outputs.apkfile }}
58 |
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文](README.zh.md)
2 | # snap_saver
3 |
4 | A camera app that makes it easier to take photos for selected albums or directories.
5 |
6 |
11 |
12 |
13 | ## Getting Started
14 |
15 | [
](https://f-droid.org/packages/lying.fengfeng.snapsaver/)
18 | Or download the latest version from the [Release page](https://github.com/NielsLee/SnapSaver/releases/latest).
19 |
20 | 1. Launch the app and grant camera permission.
21 | 2. Press the floating button in the lower right corner and select the storage path and button name of the Saver button in the pop-up dialog box.
22 | 3. After entering the information, click OK, and the Saver button will remain on the homepage of the app.
23 | 4. Press the Saver button you just created, and the photo on the camera preview interface will be taken and saved to the path saved by the Saver button.
24 | 5. You can create as many Saver buttons as you want, assign different paths to them, and then easily shoot from the homepage of the app and save the results to different paths!
25 |
26 | ## Notes
27 | **On many Android devices, after capturing and storing a photo in a specified directory, it may not be immediately visible in the gallery. It could take some time or even a reboot before the photo appears in the gallery app. The author developed this software out of personal interest and cannot dedicate too much effort to solving this issue at the moment. If anyone reading this has a good solution, feel free to discuss it with the author via an Issue. Thank you very much!😊**
28 |
29 |
30 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | namespace "lying.fengfeng.snapsaver.snap_saver"
27 | compileSdkVersion 35
28 | ndkVersion flutter.ndkVersion
29 |
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 |
35 | kotlinOptions {
36 | jvmTarget = '1.8'
37 | freeCompilerArgs += ['-Xjvm-default=all']
38 | }
39 |
40 | sourceSets {
41 | main.java.srcDirs += 'src/main/kotlin'
42 | }
43 |
44 | defaultConfig {
45 | applicationId "lying.fengfeng.snapsaver"
46 | minSdkVersion 21
47 | targetSdkVersion 34
48 | versionCode flutterVersionCode.toInteger()
49 | versionName flutterVersionName
50 | }
51 |
52 | signingConfigs {
53 | release{
54 | storeFile file("../../fengfengkeystore.jks")
55 | storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD")
56 | keyAlias System.getenv("RELEASE_KEYSTORE_ALIAS")
57 | keyPassword System.getenv("RELEASE_KEY_PASSWORD")
58 | }
59 | }
60 |
61 | buildTypes {
62 | release {
63 | signingConfig signingConfigs.release
64 | }
65 | }
66 | dependenciesInfo {
67 | includeInApk = false
68 | includeInBundle = false
69 | }
70 | }
71 |
72 | flutter {
73 | source '../..'
74 | }
75 |
76 | dependencies {}
77 |
--------------------------------------------------------------------------------
/lib/dialog/remove_saver_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:snap_saver/entity/saver.dart';
3 | import 'package:vibration/vibration.dart';
4 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
5 |
6 | class RemoveSaverDialog extends StatelessWidget {
7 | Saver saver;
8 | RemoveSaverDialog({super.key, required this.saver});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return AlertDialog(
13 | title: Row(
14 | children: [
15 | Text(AppLocalizations.of(context)!.removeSaver),
16 | Spacer(),
17 | if (saver.color != null)
18 | Icon(
19 | Icons.folder,
20 | color: Color(saver.color!),
21 | )
22 | ],
23 | ),
24 | content: Column(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | mainAxisSize: MainAxisSize.min,
27 | children: [
28 | Text(AppLocalizations.of(context)!.name + ":"),
29 | Text(
30 | saver.name,
31 | style: TextStyle(
32 | fontSize: 20,
33 | ),
34 | ),
35 | Padding(padding: EdgeInsets.all(8)),
36 | Text(AppLocalizations.of(context)!.path + ":"),
37 | Column(
38 | crossAxisAlignment: CrossAxisAlignment.start,
39 | mainAxisSize: MainAxisSize.min,
40 | children: saver.paths.map((path) {
41 | return Text(path);
42 | }).toList(),
43 | )
44 | ],
45 | ),
46 | actions: [
47 | // cancel button
48 | TextButton(
49 | child: Text(AppLocalizations.of(context)!.cancel),
50 | onPressed: () {
51 | Vibration.vibrate(amplitude: 255, duration: 5);
52 | Navigator.of(context).pop();
53 | },
54 | ),
55 |
56 | // ok button
57 | TextButton(
58 | child: Text(AppLocalizations.of(context)!.ok),
59 | onPressed: () {
60 | Vibration.vibrate(amplitude: 255, duration: 5);
61 | Navigator.of(context).pop(true);
62 | },
63 | ),
64 | ],
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSCameraUsageDescription
6 | Explanation on why the camera access is needed.
7 | NSPhotoLibraryAddUsageDescription
8 | Explanation on why the NSPhotoLibraryAddUsageDescription access is needed.
9 | NSPhotoLibraryUsageDescription
10 | 需要访问您的相册以选择照片
11 | NSMicrophoneUsageDescription
12 | Explanation on why the microphone access is needed.
13 | CFBundleDevelopmentRegion
14 | $(DEVELOPMENT_LANGUAGE)
15 | CFBundleDisplayName
16 | Snap Saver
17 | CFBundleExecutable
18 | $(EXECUTABLE_NAME)
19 | CFBundleIdentifier
20 | $(PRODUCT_BUNDLE_IDENTIFIER)
21 | CFBundleInfoDictionaryVersion
22 | 6.0
23 | CFBundleName
24 | snap_saver
25 | CFBundlePackageType
26 | APPL
27 | CFBundleShortVersionString
28 | $(FLUTTER_BUILD_NAME)
29 | CFBundleSignature
30 | ????
31 | CFBundleVersion
32 | $(FLUTTER_BUILD_NUMBER)
33 | LSRequiresIPhoneOS
34 |
35 | UILaunchStoryboardName
36 | LaunchScreen
37 | UIMainStoryboardFile
38 | Main
39 | UISupportedInterfaceOrientations
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationLandscapeLeft
43 | UIInterfaceOrientationLandscapeRight
44 |
45 | UISupportedInterfaceOrientations~ipad
46 |
47 | UIInterfaceOrientationPortrait
48 | UIInterfaceOrientationPortraitUpsideDown
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | CADisableMinimumFrameDurationOnPhone
53 |
54 | UIApplicationSupportsIndirectInputEvents
55 |
56 | UIFileSharingEnabled
57 |
58 | NSDocumentsFolderUsageDescription
59 | NSDocumentsFolderUsageDescription
60 | NSFileProtectionCompleteUntilFirstUserAuthentication
61 | NSFileProtectionCompleteUntilFirstUserAuthentication
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
22 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/lib/viewmodel/home_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:snap_saver/db/SaverDatabase.dart';
4 | import 'package:snap_saver/entity/saver.dart';
5 | import 'package:shared_preferences/shared_preferences.dart';
6 |
7 | class HomeViewModel extends ChangeNotifier {
8 | final List _saverList = [];
9 | Color _seedColor = Colors.green;
10 | int _resolution = 0;
11 | static const String _resolutionKey = 'resolution';
12 |
13 | HomeViewModel() {
14 | _initSavers();
15 | _loadSeedColor();
16 | _loadResolution();
17 | }
18 |
19 | List get savers => _saverList;
20 | Color get seedColor => _seedColor;
21 | int get resolution => _resolution;
22 |
23 | Future _loadSeedColor() async {
24 | final prefs = await SharedPreferences.getInstance();
25 | final colorValue = prefs.getInt('seed_color') ?? Colors.green.value;
26 | _seedColor = Color(colorValue);
27 | notifyListeners();
28 | }
29 |
30 | Future updateSeedColor(Color color) async {
31 | _seedColor = color;
32 | final prefs = await SharedPreferences.getInstance();
33 | await prefs.setInt('seed_color', color.value);
34 | notifyListeners();
35 | }
36 |
37 | Future _loadResolution() async {
38 | final prefs = await SharedPreferences.getInstance();
39 | _resolution = prefs.getInt(_resolutionKey) ?? 2; // middle one
40 | notifyListeners();
41 | }
42 |
43 | Future updateResolution(int value) async {
44 | if (value < 0 || value > 5) return;
45 | _resolution = value;
46 | final prefs = await SharedPreferences.getInstance();
47 | await prefs.setInt(_resolutionKey, value);
48 | notifyListeners();
49 | }
50 |
51 | int addSaver(Saver newSaver, BuildContext context) {
52 | if (_saverList.map((e) => e.name).contains(newSaver.name)) {
53 | return 0;
54 | }
55 | _saverList.add(newSaver);
56 | SaverDatabase().insertSaver(newSaver);
57 | notifyListeners();
58 | return 1;
59 | }
60 |
61 | void removeSaver(Saver saver) {
62 | _saverList.remove(saver);
63 | SaverDatabase().deleteSaver(saver);
64 | notifyListeners();
65 | }
66 |
67 | void _initSavers() async {
68 | final existedSavers = await SaverDatabase().getAllSavers();
69 | _saverList.addAll(existedSavers);
70 | notifyListeners();
71 | }
72 |
73 | int updateSaver(Saver updatedSaver) {
74 | final index =
75 | _saverList.indexWhere((saver) => saver.name == updatedSaver.name);
76 |
77 | if (index != -1) {
78 | _saverList[index] = updatedSaver;
79 |
80 | SaverDatabase().updateSaver(updatedSaver);
81 | notifyListeners();
82 | return 1; // success
83 | } else {
84 | return 0;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
65 |
71 |
72 |
73 |
74 |
80 |
82 |
88 |
89 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/assets/icons/snapsaver_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: snap_saver
2 | description: "A camera app that makes it easier to take photos for selected albums or directories."
3 | # The following line prevents the package from being accidentally published to
4 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
6 |
7 | # The following defines the version and build number for your application.
8 | # A version number is three numbers separated by dots, like 1.2.43
9 | # followed by an optional build number separated by a +.
10 | # Both the version and the builder number may be overridden in flutter
11 | # build by specifying --build-name and --build-number, respectively.
12 | # In Android, build-name is used as versionName while build-number used as versionCode.
13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
15 | # Read more about iOS versioning at
16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
17 | # In Windows, build-name is used as the major, minor, and patch parts
18 | # of the product and file versions while build-number is used as the build suffix.
19 | version: 0.9.0+10
20 |
21 | environment:
22 | sdk: '>=3.3.4 <4.0.0'
23 |
24 | # Dependencies specify other packages that your package needs in order to work.
25 | # To automatically upgrade your package dependencies to the latest versions
26 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
27 | # dependencies can be manually updated by changing the version numbers below to
28 | # the latest version available on pub.dev. To see which dependencies have newer
29 | # versions available, run `flutter pub outdated`.
30 | dependencies:
31 | flutter:
32 | sdk: flutter
33 | shared_preferences: ^2.2.2
34 |
35 | # The following adds the Cupertino Icons font to your application.
36 | # Use with the CupertinoIcons class for iOS style icons.
37 | cupertino_icons: ^1.0.6
38 | camera: ^0.11.0+1
39 | path_provider: ^2.1.3
40 | path: ^1.9.0
41 | dynamic_color: ^1.7.0
42 | flutter_staggered_grid_view: ^0.7.0
43 | provider: ^6.1.2
44 | file_picker: ^8.0.5
45 | sqflite: ^2.3.3+1
46 | audioplayers: ^6.0.0
47 | url_launcher: ^6.3.0
48 | flutter_localization: ^0.2.0
49 | flutter_launcher_icons: ^0.13.1
50 | vibration: ^1.8.3
51 | intl: any
52 | fluttertoast: ^8.2.5
53 | permission_handler: ^11.3.1
54 | image: ^4.2.0
55 |
56 | dev_dependencies:
57 | flutter_test:
58 | sdk: flutter
59 | flutter_localizations:
60 | sdk: flutter
61 |
62 | flutter_launcher_icons:
63 | android: "launcher_icon"
64 | ios: true
65 | image_path: "assets/icons/snapsaver_icon.png"
66 |
67 |
68 | # The "flutter_lints" package below contains a set of recommended lints to
69 | # encourage good coding practices. The lint set provided by the package is
70 | # activated in the `analysis_options.yaml` file located at the root of your
71 | # package. See that file for information about deactivating specific lint
72 | # rules and activating additional ones.
73 | flutter_lints: ^3.0.0
74 |
75 | # For information on the generic Dart part of this file, see the
76 | # following page: https://dart.dev/tools/pub/pubspec
77 |
78 | # The following section is specific to Flutter packages.
79 | flutter:
80 | assets:
81 | - assets/sounds/camera_shutter.mp3
82 | - assets/icons/github_mark.png
83 | - assets/icons/snapsaver_icon.svg
84 | generate: true
85 |
86 | # The following line ensures that the Material Icons font is
87 | # included with your application, so that you can use the icons in
88 | # the material Icons class.
89 | uses-material-design: true
90 |
91 | # To add assets to your application, add an assets section, like this:
92 | # assets:
93 | # - images/a_dot_burr.jpeg
94 | # - images/a_dot_ham.jpeg
95 |
96 | # An image asset can refer to one or more resolution-specific "variants", see
97 | # https://flutter.dev/assets-and-images/#resolution-aware
98 |
99 | # For details regarding adding assets from package dependencies, see
100 | # https://flutter.dev/assets-and-images/#from-packages
101 |
102 | # To add custom fonts to your application, add a fonts section here,
103 | # in this "flutter" section. Each entry in this list should have a
104 | # "family" key with the font family name, and a "fonts" key with a
105 | # list giving the asset and other descriptors for the font. For
106 | # example:
107 | # fonts:
108 | # - family: Schyler
109 | # fonts:
110 | # - asset: fonts/Schyler-Regular.ttf
111 | # - asset: fonts/Schyler-Italic.ttf
112 | # style: italic
113 | # - family: Trajan Pro
114 | # fonts:
115 | # - asset: fonts/TrajanPro.ttf
116 | # - asset: fonts/TrajanPro_Bold.ttf
117 | # weight: 700
118 | #
119 | # For details regarding fonts from package dependencies,
120 | # see https://flutter.dev/custom-fonts/#from-packages
121 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - audioplayers_darwin (0.0.1):
3 | - Flutter
4 | - camera_avfoundation (0.0.1):
5 | - Flutter
6 | - device_info_plus (0.0.1):
7 | - Flutter
8 | - DKImagePickerController/Core (4.3.9):
9 | - DKImagePickerController/ImageDataManager
10 | - DKImagePickerController/Resource
11 | - DKImagePickerController/ImageDataManager (4.3.9)
12 | - DKImagePickerController/PhotoGallery (4.3.9):
13 | - DKImagePickerController/Core
14 | - DKPhotoGallery
15 | - DKImagePickerController/Resource (4.3.9)
16 | - DKPhotoGallery (0.0.19):
17 | - DKPhotoGallery/Core (= 0.0.19)
18 | - DKPhotoGallery/Model (= 0.0.19)
19 | - DKPhotoGallery/Preview (= 0.0.19)
20 | - DKPhotoGallery/Resource (= 0.0.19)
21 | - SDWebImage
22 | - SwiftyGif
23 | - DKPhotoGallery/Core (0.0.19):
24 | - DKPhotoGallery/Model
25 | - DKPhotoGallery/Preview
26 | - SDWebImage
27 | - SwiftyGif
28 | - DKPhotoGallery/Model (0.0.19):
29 | - SDWebImage
30 | - SwiftyGif
31 | - DKPhotoGallery/Preview (0.0.19):
32 | - DKPhotoGallery/Model
33 | - DKPhotoGallery/Resource
34 | - SDWebImage
35 | - SwiftyGif
36 | - DKPhotoGallery/Resource (0.0.19):
37 | - SDWebImage
38 | - SwiftyGif
39 | - file_picker (0.0.1):
40 | - DKImagePickerController/PhotoGallery
41 | - Flutter
42 | - Flutter (1.0.0)
43 | - flutter_localization (0.0.1):
44 | - Flutter
45 | - fluttertoast (0.0.2):
46 | - Flutter
47 | - Toast
48 | - path_provider_foundation (0.0.1):
49 | - Flutter
50 | - FlutterMacOS
51 | - permission_handler_apple (9.3.0):
52 | - Flutter
53 | - SDWebImage (5.21.3):
54 | - SDWebImage/Core (= 5.21.3)
55 | - SDWebImage/Core (5.21.3)
56 | - shared_preferences_foundation (0.0.1):
57 | - Flutter
58 | - FlutterMacOS
59 | - sqflite (0.0.3):
60 | - Flutter
61 | - FlutterMacOS
62 | - SwiftyGif (5.4.5)
63 | - Toast (4.1.1)
64 | - url_launcher_ios (0.0.1):
65 | - Flutter
66 | - vibration (1.7.5):
67 | - Flutter
68 |
69 | DEPENDENCIES:
70 | - audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/ios`)
71 | - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
72 | - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
73 | - file_picker (from `.symlinks/plugins/file_picker/ios`)
74 | - Flutter (from `Flutter`)
75 | - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`)
76 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
77 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
78 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
79 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
80 | - sqflite (from `.symlinks/plugins/sqflite/darwin`)
81 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
82 | - vibration (from `.symlinks/plugins/vibration/ios`)
83 |
84 | SPEC REPOS:
85 | trunk:
86 | - DKImagePickerController
87 | - DKPhotoGallery
88 | - SDWebImage
89 | - SwiftyGif
90 | - Toast
91 |
92 | EXTERNAL SOURCES:
93 | audioplayers_darwin:
94 | :path: ".symlinks/plugins/audioplayers_darwin/ios"
95 | camera_avfoundation:
96 | :path: ".symlinks/plugins/camera_avfoundation/ios"
97 | device_info_plus:
98 | :path: ".symlinks/plugins/device_info_plus/ios"
99 | file_picker:
100 | :path: ".symlinks/plugins/file_picker/ios"
101 | Flutter:
102 | :path: Flutter
103 | flutter_localization:
104 | :path: ".symlinks/plugins/flutter_localization/ios"
105 | fluttertoast:
106 | :path: ".symlinks/plugins/fluttertoast/ios"
107 | path_provider_foundation:
108 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
109 | permission_handler_apple:
110 | :path: ".symlinks/plugins/permission_handler_apple/ios"
111 | shared_preferences_foundation:
112 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
113 | sqflite:
114 | :path: ".symlinks/plugins/sqflite/darwin"
115 | url_launcher_ios:
116 | :path: ".symlinks/plugins/url_launcher_ios/ios"
117 | vibration:
118 | :path: ".symlinks/plugins/vibration/ios"
119 |
120 | SPEC CHECKSUMS:
121 | audioplayers_darwin: 877d9a4d06331c5c374595e46e16453ac7eafa40
122 | camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4
123 | device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
124 | DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
125 | DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
126 | file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
127 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
128 | flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4
129 | fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
130 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
131 | permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
132 | SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
133 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
134 | sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
135 | SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
136 | Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
137 | url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
138 | vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241
139 |
140 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
141 |
142 | COCOAPODS: 1.15.2
143 |
--------------------------------------------------------------------------------
/lib/dialog/more_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
5 | import 'package:intl/intl.dart';
6 | import 'package:snap_saver/entity/more.dart';
7 |
8 | class MoreDialog extends StatefulWidget {
9 | const MoreDialog({super.key});
10 |
11 | @override
12 | State createState() => MoreDialogState();
13 | }
14 |
15 | class MoreDialogState extends State {
16 | TextEditingController fileNameController = TextEditingController();
17 | String examplePhotoName = "";
18 |
19 | int _selectedIndex = 0;
20 |
21 | void _refreshExamplePhotoName() {
22 | String newValue = fileNameController.text;
23 | switch (_selectedIndex) {
24 | case 0:
25 | {
26 | examplePhotoName = newValue + "1" + '\n' + newValue + "2";
27 | break;
28 | }
29 | case 1:
30 | {
31 | DateTime now = DateTime.now();
32 | DateTime yesterday = now.subtract(const Duration(days: 1));
33 |
34 | String nowStr = DateFormat('yyyyMMddHHmmss').format(now);
35 | String yesterdayStr = DateFormat('yyyyMMddHHmmss').format(yesterday);
36 | examplePhotoName = newValue + nowStr + '\n' + newValue + yesterdayStr;
37 | break;
38 | }
39 | case 2:
40 | {
41 | examplePhotoName = newValue + "_1" + '\n' + newValue + "_2";
42 | break;
43 | }
44 | case 3:
45 | {
46 | DateTime now = DateTime.now();
47 | DateTime yesterday = now.subtract(const Duration(days: 1));
48 |
49 | String nowStr = DateFormat('yyyyMMddHHmmss').format(now);
50 | String yesterdayStr = DateFormat('yyyyMMddHHmmss').format(yesterday);
51 | examplePhotoName =
52 | newValue + '_' + nowStr + '\n' + newValue + '_' + yesterdayStr;
53 | break;
54 | }
55 | case 4:
56 | {
57 | examplePhotoName = newValue + "-1" + '\n' + newValue + "-2";
58 | break;
59 | }
60 | case 5:
61 | {
62 | DateTime now = DateTime.now();
63 | DateTime yesterday = now.subtract(const Duration(days: 1));
64 |
65 | String nowStr = DateFormat('yyyyMMddHHmmss').format(now);
66 | String yesterdayStr = DateFormat('yyyyMMddHHmmss').format(yesterday);
67 | examplePhotoName =
68 | newValue + '-' + nowStr + '\n' + newValue + '-' + yesterdayStr;
69 | break;
70 | }
71 | }
72 | }
73 |
74 | @override
75 | Widget build(BuildContext context) {
76 | final List _typeList = [
77 | AppLocalizations.of(context)!.photoIndex,
78 | AppLocalizations.of(context)!.photoTimestamp,
79 | "_" + AppLocalizations.of(context)!.photoIndex,
80 | "_" + AppLocalizations.of(context)!.photoTimestamp,
81 | "-" + AppLocalizations.of(context)!.photoIndex,
82 | "-" + AppLocalizations.of(context)!.photoTimestamp,
83 | ];
84 |
85 | return AlertDialog(
86 | title: Text(AppLocalizations.of(context)!.more),
87 | content: Column(
88 | mainAxisSize: MainAxisSize.min,
89 | crossAxisAlignment: CrossAxisAlignment.start,
90 | children: [
91 | Text(AppLocalizations.of(context)!.photoName),
92 | Row(
93 | children: [
94 | Expanded(
95 | flex: 1,
96 | child: TextField(
97 | controller: fileNameController,
98 | onTap: () {},
99 | onChanged: (newValue) {
100 | setState(() {
101 | _refreshExamplePhotoName();
102 | });
103 | },
104 | decoration: InputDecoration(
105 | label: Text(
106 | AppLocalizations.of(context)!.photoNameDescription),
107 | floatingLabelBehavior: FloatingLabelBehavior.never,
108 | border: const OutlineInputBorder(
109 | borderRadius:
110 | BorderRadius.all(Radius.circular(12)))),
111 | )),
112 | Text(" + "),
113 | DropdownButton(
114 | value: _typeList[_selectedIndex],
115 | items: _typeList.map((String item) {
116 | return DropdownMenuItem(
117 | value: item,
118 | child: Text(item),
119 | );
120 | }).toList(),
121 | onChanged: (String? newValue) {
122 | setState(() {
123 | if (newValue != null) {
124 | _selectedIndex = _typeList.indexOf(newValue);
125 | }
126 | _refreshExamplePhotoName();
127 | });
128 | },
129 | )
130 | ],
131 | ),
132 | Text(AppLocalizations.of(context)!.photoNameExample + ':'),
133 | Text(examplePhotoName)
134 | ],
135 | ),
136 | actions: [
137 | TextButton(
138 | onPressed: () {
139 | Navigator.of(context).pop();
140 | },
141 | child: Text(AppLocalizations.of(context)!.cancel)),
142 | TextButton(
143 | onPressed: () {
144 | var name = fileNameController.text;
145 | if (name.isEmpty) {
146 | // no name input, use default
147 | Navigator.of(context).pop();
148 | return;
149 | }
150 | var more = More(photoName: name, suffixType: _selectedIndex);
151 | Navigator.of(context).pop(more);
152 | },
153 | child: Text(AppLocalizations.of(context)!.ok)),
154 | ],
155 | );
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/lib/db/SaverDatabase.dart:
--------------------------------------------------------------------------------
1 | import 'package:path/path.dart';
2 | import 'package:snap_saver/constants.dart';
3 | import 'package:sqflite/sqflite.dart';
4 |
5 | import '../entity/saver.dart';
6 |
7 | class SaverDatabase {
8 | late Database _database;
9 | bool _isInitialized = false;
10 |
11 | Future get database async {
12 | if (_isInitialized) return _database;
13 |
14 | _database = await _init();
15 | _isInitialized = true;
16 | return _database;
17 | }
18 |
19 | Future insertSaver(Saver saver) async {
20 | final db = await database;
21 |
22 | final saverMap = {
23 | 'path': null,
24 | 'name': saver.name,
25 | 'color': saver.color,
26 | 'count': saver.count,
27 | 'photoName': saver.photoName,
28 | 'suffixType': saver.suffixType
29 | };
30 | // insert saver to saver table
31 | db.insert(Constants.saverTableName, saverMap,
32 | conflictAlgorithm: ConflictAlgorithm.rollback);
33 |
34 | // insert paths to path table
35 | final saverName = saver.name;
36 | final pathList = saver.paths;
37 | for (String path in pathList) {
38 | db.insert(
39 | Constants.pathTableName,
40 | {
41 | "saver_name": saverName,
42 | "path": path,
43 | },
44 | conflictAlgorithm: ConflictAlgorithm.rollback);
45 | }
46 | return;
47 | }
48 |
49 | Future deleteSaver(Saver saver) async {
50 | final db = await database;
51 |
52 | db.delete(Constants.saverTableName,
53 | where: 'name = ?', whereArgs: [saver.name]);
54 |
55 | db.delete(Constants.pathTableName,
56 | where: 'saver_name = ?', whereArgs: [saver.name]);
57 | }
58 |
59 | Future> getAllSavers() async {
60 | final db = await database;
61 | List resultList = [];
62 |
63 | final List