commit a768c2fa73cfb642551573b53bc47ef33864e1ff Author: 住京华 <1624109111@qq.com> Date: Sat Aug 24 16:55:47 2024 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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 +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# 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..b2627e8 --- /dev/null +++ b/.metadata @@ -0,0 +1,30 @@ +# 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: "7c6b7e9ca485f7eaaed913c6bb50f4be6da47e30" + channel: "beta" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 7c6b7e9ca485f7eaaed913c6bb50f4be6da47e30 + base_revision: 7c6b7e9ca485f7eaaed913c6bb50f4be6da47e30 + - platform: windows + create_revision: 7c6b7e9ca485f7eaaed913c6bb50f4be6da47e30 + base_revision: 7c6b7e9ca485f7eaaed913c6bb50f4be6da47e30 + + # 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/README.en.md b/README.en.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..d114615 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# 📔 心绪日记 + +[简体中文](README.md) | [English](README.en.md) + +「心绪日记」 是一个由 Flutter 构建的跨平台日记应用,采用 Material Design 设计。它支持富文本编辑,让你可以轻松地在 Android 和 iOS 设备上创建和管理个人日记。 + +## ✨ 功能特性 + +- **跨平台支持**:🌍 兼容 Android、iOS、Windows(即将支持)。 +- **Material Design**:🎨 界面直观且用户友好,遵循 Material Design 设计规范。 +- **富文本编辑**:📝 支持加粗、斜体、下划线等多种格式的文本编辑。 +- **多媒体附件**:📷 可以为你的日记添加图片、音频和视频。 +- **搜索和标签**:🔍 轻松搜索和通过标签分类你的日记。 +- **可自定义主题**:🌈 支持选择浅色和深色模式,或自定义主题。 +- **数据安全**:🔒 通过密码来保障你的日记安全。 +- **云同步(即将支持)**:☁️ 支持在多个设备间同步日记。 + +## 📸 应用截图 + +### Andriod + +| ![](res/screenshot/phone1.png) | ![](res/screenshot/phone2.png) | +| ------------------------------ | ------------------------------ | + + + +## 🚀 安装指南 + +### 环境要求 + +- Flutter SDK (>= 3.24.0) +- Dart (>= 3.5.0) +- 兼容的 IDE(如 Android Studio、Visual Studio Code) + +### 安装步骤 + +1. **克隆仓库**: + + ```bash + + cd mydiaryapp + ``` + +2. **安装依赖**: + + ```bash + flutter pub get + ``` + +3. **运行应用**: + + ```bash + flutter run + ``` + +4. **打包发布**: + + - Android: `flutter build apk` + - iOS: `flutter build ios` + +## 📝 使用说明 + +安装完成后,你可以通过点击“新建日记”按钮开始创建日记条目。使用富文本编辑器来格式化你的内容,添加多媒体附件,并通过标签进行组织。 + +## 🤝 贡献指南 + +欢迎贡献!请按照以下步骤进行贡献: + +1. Fork 本仓库。 +2. 创建一个新分支(`git checkout -b feature-branch-name`)。 +3. 提交你的修改(`git commit -am 'Add some feature'`)。 +4. 推送到分支(`git push origin feature-branch-name`)。 +5. 创建一个 Pull Request。 + +请确保你的代码遵循 [Flutter 风格指南](https://flutter.dev/docs/development/tools/formatting) 并包含适当的测试。 + +## 📄 许可证 + +此项目基于 MIT 许可证进行许可,详情请参阅 [LICENSE](LICENSE) 文件。 + +## 💖 鸣谢 + +- 感谢 Flutter 团队提供出色的框架。 +- 特别感谢开源社区的宝贵贡献。 \ No newline at end of file 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..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..2d120a7 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,83 @@ +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" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "cn.yooss.mood_diary" + compileSdk = 35 + ndkVersion = "26.3.11579264" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + signingConfigs { + config { + storeFile file('key.jks') + storePassword localProperties.getProperty('storePassword') + keyPassword localProperties.getProperty('keyPassword') + keyAlias 'key0' + + enableV3Signing true + } + } + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "cn.yooss.moodiary" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = 24 + targetSdk = 35 + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + ndk { + abiFilters 'arm64-v8a','x86_64' + } + } + + buildTypes { + release { + signingConfig = signingConfigs.config + } + debug { + signingConfig = signingConfigs.config + } + profile { + signingConfig = signingConfigs.config + } + } +} + +flutter { + source = "../.." +} +dependencies { + implementation 'com.tencent.shiply:upgrade:2.2.0' + implementation 'com.tencent.shiply:upgrade-diff-pkg-patch:2.2.0' + implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' + implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1' + implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1' + implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1' + implementation 'com.github.gzu-liyujiang:Android_CN_OAID:4.2.9' +} + diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..d9dc172 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,29 @@ +-keep class com.tencent.** { *; } +-keep class com.google.gson.** { *; } +-dontwarn com.tencent.** +-keep class repeackage.com.uodis.opendevice.aidl.** { *; } +-keep interface repeackage.com.uodis.opendevice.aidl.** { *; } +-keep class repeackage.com.asus.msa.SupplementaryDID.** { *; } +-keep interface repeackage.com.asus.msa.SupplementaryDID.** { *; } +-keep class repeackage.com.bun.lib.** { *; } +-keep interface repeackage.com.bun.lib.** { *; } +-keep class repeackage.com.heytap.openid.** { *; } +-keep interface repeackage.com.heytap.openid.** { *; } +-keep class repeackage.com.samsung.android.deviceidservice.** { *; } +-keep interface repeackage.com.samsung.android.deviceidservice.** { *; } +-keep class repeackage.com.zui.deviceidservice.** { *; } +-keep interface repeackage.com.zui.deviceidservice.** { *; } +-keep class repeackage.com.coolpad.deviceidsupport.** { *; } +-keep interface repeackage.com.coolpad.deviceidsupport.** { *; } +-keep class repeackage.com.android.creator.** { *; } +-keep interface repeackage.com.android.creator.** { *; } +-keep class repeackage.com.google.android.gms.ads.identifier.internal.** { *; } +-keep interface repeackage.com.google.android.gms.ads.identifier.internal.* { *; } +-keep class repeackage.com.oplus.stdid.** {*; } +-keep interface repeackage.com.oplus.stdid.** {*; } +-keep class com.huawei.hms.ads.** {*; } +-keep interface com.huawei.hms.ads.** {*; } +-keep class com.hihonor.ads.** {*; } +-keep interface com.hihonor.ads.** {*; } +-keep class repeackage.com.qiku.id.** { *; } +-keep interface repeackage.com.qiku.id.** { *; } \ No newline at end of file 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..9d7c287 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleGetOAID.kt b/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleGetOAID.kt new file mode 100644 index 0000000..5ec4201 --- /dev/null +++ b/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleGetOAID.kt @@ -0,0 +1,16 @@ +package cn.yooss.mood_diary + +import com.github.gzuliyujiang.oaid.IGetter +import io.flutter.plugin.common.MethodChannel +import java.lang.Exception + +class HandleGetOAID(private var resultCallback: MethodChannel.Result) : IGetter { + override fun onOAIDGetComplete(result: String?) { + resultCallback.success(result); + } + + override fun onOAIDGetError(error: Exception?) { + + resultCallback.error("100", "error", error); + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleUpgradeCallback.kt b/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleUpgradeCallback.kt new file mode 100644 index 0000000..3a9dd4b --- /dev/null +++ b/android/app/src/main/kotlin/cn/yooss/mood_diary/HandleUpgradeCallback.kt @@ -0,0 +1,29 @@ +package cn.yooss.mood_diary + +import com.google.gson.Gson +import com.tencent.upgrade.bean.UpgradeStrategy +import com.tencent.upgrade.callback.UpgradeStrategyRequestCallback +import io.flutter.plugin.common.MethodChannel + +/** + * @Description + * @Author 住京华 https://yooss.cn + * @Date 2024/6/11 + */ +class HandleUpgradeCallback(private var result: MethodChannel.Result) : + UpgradeStrategyRequestCallback { + + override fun onReceiveStrategy(var1: UpgradeStrategy) { + + result.success(Gson().toJson(var1)); + } + + override fun onFail(var1: Int, var2: String) { + + } + + override fun onReceivedNoStrategy() { + result.success(null) + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/cn/yooss/mood_diary/MainActivity.kt b/android/app/src/main/kotlin/cn/yooss/mood_diary/MainActivity.kt new file mode 100644 index 0000000..91bf46a --- /dev/null +++ b/android/app/src/main/kotlin/cn/yooss/mood_diary/MainActivity.kt @@ -0,0 +1,102 @@ +package cn.yooss.mood_diary + +import android.graphics.Color +import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat +import com.github.gzuliyujiang.oaid.DeviceID +import com.tencent.shiply.processor.DiffPkgHandler +import com.tencent.shiply.processor.OriginBasePkgFile +import com.tencent.upgrade.bean.UpgradeConfig +import com.tencent.upgrade.core.UpgradeManager +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodChannel + + +class MainActivity : FlutterFragmentActivity() { + + + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, "view_channel" + ).setMethodCallHandler { call, result -> + if (call.method == "setSystemUIVisibility") { + setSystemUIVisibility() + result.success(null) + } else { + result.notImplemented() + } + } + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, "shiply_channel" + ).setMethodCallHandler { call, result -> + when (call.method) { + "initShiply" -> { + initShiply() + result.success(null) + } + + + "checkUpdate" -> { + checkUpdate(result) + } + + "startDownload" -> { + startDownload() + result.success(null) + } + + else -> { + result.notImplemented() + } + } + } + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, "oaid_channel" + ).setMethodCallHandler { call, result -> + when (call.method) { + "getOAID" -> { + getOAID(result) + } + + else -> { + result.notImplemented() + } + } + } + + } + + + private fun checkUpdate(result: MethodChannel.Result) { + UpgradeManager.getInstance().checkUpgrade(true, null, HandleUpgradeCallback(result)) + } + + private fun startDownload() { + UpgradeManager.getInstance().startDownload() + } + + private fun initShiply() { + val builder: UpgradeConfig.Builder = UpgradeConfig.Builder() + val config: UpgradeConfig = + builder.appId("").appKey("") + .diffPkgHandler(DiffPkgHandler()).basePkgFileForDiffUpgrade(OriginBasePkgFile()) + .cacheExpireTime(1000 * 60 * 60 * 6).userId("").build() + UpgradeManager.getInstance().init(application, config) + } + + private fun getOAID(resultCallback: MethodChannel.Result) { + if (DeviceID.supportedOAID(application)) { + DeviceID.getOAID(application, HandleGetOAID(resultCallback)); + } + } + + private fun setSystemUIVisibility() { + val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView) + WindowCompat.setDecorFitsSystemWindows(window, false) + //window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) + window.navigationBarColor = Color.TRANSPARENT + } + +} 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..cd8c23c --- /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-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..31af4ff --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..4ae7d12 --- /dev/null +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..1773772 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp new file mode 100644 index 0000000..d0534d7 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..e1f9062 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..341577c Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..de0ebd9 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp new file mode 100644 index 0000000..60b8e35 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..295d7ed Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..aba352f Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..1bae111 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp new file mode 100644 index 0000000..34458b2 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..eccf4b1 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..575c497 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..0060480 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp new file mode 100644 index 0000000..6c466a1 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..4cd9188 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..08a8131 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..2168208 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp new file mode 100644 index 0000000..f73db9b Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000..ec51b5b Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..83e23b9 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/app/src/main/res/values-en/appname.xml b/android/app/src/main/res/values-en/appname.xml new file mode 100644 index 0000000..9369d51 --- /dev/null +++ b/android/app/src/main/res/values-en/appname.xml @@ -0,0 +1,4 @@ + + + Moodiary + \ No newline at end of file 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/appname.xml b/android/app/src/main/res/values-zh/appname.xml new file mode 100644 index 0000000..983a7d0 --- /dev/null +++ b/android/app/src/main/res/values-zh/appname.xml @@ -0,0 +1,4 @@ + + + 心绪日记 + \ No newline at end of file diff --git a/android/app/src/main/res/values/appname.xml b/android/app/src/main/res/values/appname.xml new file mode 100644 index 0000000..983a7d0 --- /dev/null +++ b/android/app/src/main/res/values/appname.xml @@ -0,0 +1,4 @@ + + + 心绪日记 + \ No newline at end of file diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file 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/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..bb6ab93 --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file 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 b/android/build.gradle new file mode 100644 index 0000000..0892f70 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,27 @@ +allprojects { + repositories { + google() + mavenCentral() + maven { url "https://maven.aliyun.com/repository/google" } + maven { url "https://maven.aliyun.com/repository/jcenter" } + maven { url "https://maven.aliyun.com/nexus/content/groups/public" } + maven { url "https://tencent-tds-maven.pkg.coding.net/repository/shiply/repo" } + maven { url "https://storage.googleapis.com/download.flutter.io" } + maven { url 'https://jitpack.io' } + maven { url 'https://developer.hihonor.com/repo' } + maven { url 'https://developer.huawei.com/repo' } + + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..87c9be3 --- /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\://mirrors.cloud.tencent.com/gradle/gradle-8.6-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..a50093a --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,30 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + maven { url "https://maven.aliyun.com/repository/google" } + maven { url "https://maven.aliyun.com/repository/jcenter" } + maven { url "https://maven.aliyun.com/nexus/content/groups/public" } + maven { url "https://tencent-tds-maven.pkg.coding.net/repository/shiply/repo" } + maven { url "https://storage.googleapis.com/download.flutter.io" } + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.4.0" apply false + id "org.jetbrains.kotlin.android" version "2.0.0" apply false +} + +include ":app" diff --git a/fonts/qweather-icons.ttf b/fonts/qweather-icons.ttf new file mode 100644 index 0000000..2945de7 Binary files /dev/null and b/fonts/qweather-icons.ttf differ 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..7c56964 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.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..7dd2ef7 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*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 */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.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 = ""; }; + 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 = ""; }; + 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 */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.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, + 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 */, + 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 */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 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 = 12.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 = cn.yooss.moodDiary; + 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 = cn.yooss.moodDiary.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 = cn.yooss.moodDiary.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 = cn.yooss.moodDiary.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 = 12.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 = 12.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 = cn.yooss.moodDiary; + 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 = cn.yooss.moodDiary; + 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..8e3ca5d --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..9074fee --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} 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 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ 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 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ 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 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ 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 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ 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 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ 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 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ 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 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ 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 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ 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 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ 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 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ 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 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ 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..529b141 --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Mood Diary + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + mood_diary + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + 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/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/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..db88673 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: intl_zh.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/lib/api/api.dart b/lib/api/api.dart new file mode 100644 index 0000000..151e8d0 --- /dev/null +++ b/lib/api/api.dart @@ -0,0 +1,77 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/hitokoto.dart'; +import 'package:mood_diary/common/models/hunyuan.dart'; +import 'package:mood_diary/common/models/image.dart'; +import 'package:mood_diary/common/models/weather.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class Api { + Api._(); + + static final Api _instance = Api._(); + + factory Api() => _instance; + + Future?> getHunYuan(String id, String key, List messages, int model) async { + //获取时间戳 + var timestamp = DateTime.now().millisecondsSinceEpoch; + var hunyuanModel = switch (model) { + 0 => 'hunyuan-lite', + 1 => 'hunyuan-standard', + 2 => 'hunyuan-pro', + _ => 'hunyuan-lite', + }; + //请求正文 + var body = { + 'Model': hunyuanModel, + 'Messages': messages.map((value) => value.toMap()).toList(), + 'Stream': true, + }; + + //获取签名 + var authorization = Utils().signatureUtil.generateSignature(id, key, timestamp, body); + //构造请求头 + var header = PublicHeader('ChatCompletions', timestamp ~/ 1000, '2023-09-01', authorization); + //发起请求 + return await Utils().httpUtil.postStream('https://hunyuan.tencentcloudapi.com', header: header.toMap(), data: body); + } + + Future getImageData(String url) async { + return (await Utils().httpUtil.get(url, type: ResponseType.bytes)).data; + } + + Future?> updateWeather() async { + var position = await Utils().permissionUtil.getLocation(); + if (position != null) { + var local = Localizations.localeOf(Get.context!); + var parameters = { + 'location': '${position.longitude},${position.altitude}', + 'key': Utils().prefUtil.getValue('qweatherKey'), + 'lang': local + }; + var res = await Utils().httpUtil.get('https://devapi.qweather.com/v7/weather/now', parameters: parameters); + var weather = await compute(WeatherResponse.fromJson, res.data as Map); + return [ + weather.now!.icon!, + weather.now!.temp!, + weather.now!.text!, + ]; + } + return null; + } + + Future?> updateHitokoto() async { + var res = await Utils().httpUtil.get('https://v1.hitokoto.cn'); + var hitokoto = await compute(HitokotoResponse.fromJson, res.data as Map); + return [hitokoto.hitokoto!]; + } + + Future?> updateImageUrl() async { + var res = await Utils().httpUtil.get('https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1'); + BingImage bingImage = await compute(BingImage.fromJson, res.data as Map); + return ['https://cn.bing.com${bingImage.images?[0].url}']; + } +} diff --git a/lib/common/models/hitokoto.dart b/lib/common/models/hitokoto.dart new file mode 100644 index 0000000..238371f --- /dev/null +++ b/lib/common/models/hitokoto.dart @@ -0,0 +1,60 @@ +class HitokotoResponse { + int? id; + String? uuid; + String? hitokoto; + String? type; + String? from; + String? fromWho; + String? creator; + int? creatorUid; + int? reviewer; + String? commitFrom; + String? createdAt; + int? length; + + HitokotoResponse( + {this.id, + this.uuid, + this.hitokoto, + this.type, + this.from, + this.fromWho, + this.creator, + this.creatorUid, + this.reviewer, + this.commitFrom, + this.createdAt, + this.length}); + + HitokotoResponse.fromJson(Map json) { + id = json["id"]; + uuid = json["uuid"]; + hitokoto = json["hitokoto"]; + type = json["type"]; + from = json["from"]; + fromWho = json["from_who"]; + creator = json["creator"]; + creatorUid = json["creator_uid"]; + reviewer = json["reviewer"]; + commitFrom = json["commit_from"]; + createdAt = json["created_at"]; + length = json["length"]; + } + + Map toJson() { + final Map data = {}; + data["id"] = id; + data["uuid"] = uuid; + data["hitokoto"] = hitokoto; + data["type"] = type; + data["from"] = from; + data["from_who"] = fromWho; + data["creator"] = creator; + data["creator_uid"] = creatorUid; + data["reviewer"] = reviewer; + data["commit_from"] = commitFrom; + data["created_at"] = createdAt; + data["length"] = length; + return data; + } +} diff --git a/lib/common/models/hunyuan.dart b/lib/common/models/hunyuan.dart new file mode 100644 index 0000000..120c1bb --- /dev/null +++ b/lib/common/models/hunyuan.dart @@ -0,0 +1,134 @@ +class PublicHeader { + String? action; + int? timestamp; + String? version; + String? authorization; + + Map toMap() { + return { + 'X-TC-Action': action, + 'X-TC-Timestamp': timestamp, + 'X-TC-Version': version, + 'Authorization': authorization, + }; + } + + PublicHeader.fromMap(Map map) { + action = map['X-TC-Action']; + timestamp = map['X-TC-Timestamp']; + version = map['X-TC-Version']; + authorization = map['Authorization']; + } + + PublicHeader(this.action, this.timestamp, this.version, this.authorization); +} + +class Message { + late String role; + late String content; + + Message(this.role, this.content); + + Map toMap() { + return {'Role': role, 'Content': content}; + } + + Message.fromMap(Map map) { + role = map['Role']; + content = map['Content']; + } +} + +class HunyuanResponse { + String? note; + List? choices; + int? created; + String? id; + Usage? usage; + + HunyuanResponse({this.note, this.choices, this.created, this.id, this.usage}); + + HunyuanResponse.fromJson(Map json) { + note = json["Note"]; + choices = json["Choices"] == null ? null : (json["Choices"] as List).map((e) => Choices.fromJson(e)).toList(); + created = json["Created"]; + id = json["Id"]; + usage = json["Usage"] == null ? null : Usage.fromJson(json["Usage"]); + } + + Map toJson() { + final Map data = {}; + data["Note"] = note; + if (choices != null) { + data["Choices"] = choices?.map((e) => e.toJson()).toList(); + } + data["Created"] = created; + data["Id"] = id; + if (usage != null) { + data["Usage"] = usage?.toJson(); + } + return data; + } +} + +class Usage { + int? promptTokens; + int? completionTokens; + int? totalTokens; + + Usage({this.promptTokens, this.completionTokens, this.totalTokens}); + + Usage.fromJson(Map json) { + promptTokens = json["PromptTokens"]; + completionTokens = json["CompletionTokens"]; + totalTokens = json["TotalTokens"]; + } + + Map toJson() { + final Map data = {}; + data["PromptTokens"] = promptTokens; + data["CompletionTokens"] = completionTokens; + data["TotalTokens"] = totalTokens; + return data; + } +} + +class Choices { + String? finishReason; + Delta? delta; + + Choices({this.finishReason, this.delta}); + + Choices.fromJson(Map json) { + finishReason = json["FinishReason"]; + delta = json["Delta"] == null ? null : Delta.fromJson(json["Delta"]); + } + + Map toJson() { + final Map data = {}; + data["FinishReason"] = finishReason; + if (delta != null) { + data["Delta"] = delta?.toJson(); + } + return data; + } +} + +class Delta { + String? role; + String? content; + + Delta({this.role, this.content}); + + Delta.fromJson(Map json) { + role = json["Role"]; + content = json["Content"]; + } + + Map toJson() { + final Map data = {}; + data["Role"] = role; + data["Content"] = content; + return data; + } +} diff --git a/lib/common/models/image.dart b/lib/common/models/image.dart new file mode 100644 index 0000000..8220d26 --- /dev/null +++ b/lib/common/models/image.dart @@ -0,0 +1,125 @@ +class BingImage { + List? images; + Tooltips? tooltips; + + BingImage({this.images, this.tooltips}); + + BingImage.fromJson(Map json) { + images = json["images"] == null ? null : (json["images"] as List).map((e) => Images.fromJson(e)).toList(); + tooltips = json["tooltips"] == null ? null : Tooltips.fromJson(json["tooltips"]); + } + + Map toJson() { + final Map data = {}; + if (images != null) { + data["images"] = images?.map((e) => e.toJson()).toList(); + } + if (tooltips != null) { + data["tooltips"] = tooltips?.toJson(); + } + return data; + } +} + +class Tooltips { + String? loading; + String? previous; + String? next; + String? walle; + String? walls; + + Tooltips({this.loading, this.previous, this.next, this.walle, this.walls}); + + Tooltips.fromJson(Map json) { + loading = json["loading"]; + previous = json["previous"]; + next = json["next"]; + walle = json["walle"]; + walls = json["walls"]; + } + + Map toJson() { + final Map data = {}; + data["loading"] = loading; + data["previous"] = previous; + data["next"] = next; + data["walle"] = walle; + data["walls"] = walls; + return data; + } +} + +class Images { + String? startdate; + String? fullstartdate; + String? enddate; + String? url; + String? urlbase; + String? copyright; + String? copyrightlink; + String? title; + String? quiz; + bool? wp; + String? hsh; + int? drk; + int? top; + int? bot; + List? hs; + + Images( + {this.startdate, + this.fullstartdate, + this.enddate, + this.url, + this.urlbase, + this.copyright, + this.copyrightlink, + this.title, + this.quiz, + this.wp, + this.hsh, + this.drk, + this.top, + this.bot, + this.hs}); + + Images.fromJson(Map json) { + startdate = json["startdate"]; + fullstartdate = json["fullstartdate"]; + enddate = json["enddate"]; + url = json["url"]; + urlbase = json["urlbase"]; + copyright = json["copyright"]; + copyrightlink = json["copyrightlink"]; + title = json["title"]; + quiz = json["quiz"]; + wp = json["wp"]; + hsh = json["hsh"]; + drk = json["drk"]; + top = json["top"]; + bot = json["bot"]; + hs = json["hs"] ?? []; + } + + Map toJson() { + final Map data = {}; + data["startdate"] = startdate; + data["fullstartdate"] = fullstartdate; + data["enddate"] = enddate; + data["url"] = url; + data["urlbase"] = urlbase; + data["copyright"] = copyright; + data["copyrightlink"] = copyrightlink; + data["title"] = title; + data["quiz"] = quiz; + data["wp"] = wp; + data["hsh"] = hsh; + data["drk"] = drk; + data["top"] = top; + data["bot"] = bot; + if (hs != null) { + data["hs"] = hs; + } + return data; + } +} diff --git a/lib/common/models/isar/category.dart b/lib/common/models/isar/category.dart new file mode 100644 index 0000000..da9334c --- /dev/null +++ b/lib/common/models/isar/category.dart @@ -0,0 +1,12 @@ +import 'package:isar/isar.dart'; + +part 'category.g.dart'; + +@collection +class Category { + @Id() + late String id; + + //分类名称 + late String categoryName; +} diff --git a/lib/common/models/isar/category.g.dart b/lib/common/models/isar/category.g.dart new file mode 100644 index 0000000..786b93c --- /dev/null +++ b/lib/common/models/isar/category.g.dart @@ -0,0 +1,635 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'category.dart'; + +// ************************************************************************** +// _IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, invalid_use_of_protected_member, lines_longer_than_80_chars, constant_identifier_names, avoid_js_rounded_ints, no_leading_underscores_for_local_identifiers, require_trailing_commas, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_in_if_null_operators, library_private_types_in_public_api, prefer_const_constructors +// ignore_for_file: type=lint + +extension GetCategoryCollection on Isar { + IsarCollection get categorys => this.collection(); +} + +const CategorySchema = IsarGeneratedSchema( + schema: IsarSchema( + name: 'Category', + idName: 'id', + embedded: false, + properties: [ + IsarPropertySchema( + name: 'id', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'categoryName', + type: IsarType.string, + ), + ], + indexes: [], + ), + converter: IsarObjectConverter( + serialize: serializeCategory, + deserialize: deserializeCategory, + deserializeProperty: deserializeCategoryProp, + ), + embeddedSchemas: [], +); + +@isarProtected +int serializeCategory(IsarWriter writer, Category object) { + IsarCore.writeString(writer, 1, object.id); + IsarCore.writeString(writer, 2, object.categoryName); + return Isar.fastHash(object.id); +} + +@isarProtected +Category deserializeCategory(IsarReader reader) { + final object = Category(); + object.id = IsarCore.readString(reader, 1) ?? ''; + object.categoryName = IsarCore.readString(reader, 2) ?? ''; + return object; +} + +@isarProtected +dynamic deserializeCategoryProp(IsarReader reader, int property) { + switch (property) { + case 1: + return IsarCore.readString(reader, 1) ?? ''; + case 2: + return IsarCore.readString(reader, 2) ?? ''; + default: + throw ArgumentError('Unknown property: $property'); + } +} + +sealed class _CategoryUpdate { + bool call({ + required String id, + String? categoryName, + }); +} + +class _CategoryUpdateImpl implements _CategoryUpdate { + const _CategoryUpdateImpl(this.collection); + + final IsarCollection collection; + + @override + bool call({ + required String id, + Object? categoryName = ignore, + }) { + return collection.updateProperties([ + id + ], { + if (categoryName != ignore) 2: categoryName as String?, + }) > + 0; + } +} + +sealed class _CategoryUpdateAll { + int call({ + required List id, + String? categoryName, + }); +} + +class _CategoryUpdateAllImpl implements _CategoryUpdateAll { + const _CategoryUpdateAllImpl(this.collection); + + final IsarCollection collection; + + @override + int call({ + required List id, + Object? categoryName = ignore, + }) { + return collection.updateProperties(id, { + if (categoryName != ignore) 2: categoryName as String?, + }); + } +} + +extension CategoryUpdate on IsarCollection { + _CategoryUpdate get update => _CategoryUpdateImpl(this); + + _CategoryUpdateAll get updateAll => _CategoryUpdateAllImpl(this); +} + +sealed class _CategoryQueryUpdate { + int call({ + String? categoryName, + }); +} + +class _CategoryQueryUpdateImpl implements _CategoryQueryUpdate { + const _CategoryQueryUpdateImpl(this.query, {this.limit}); + + final IsarQuery query; + final int? limit; + + @override + int call({ + Object? categoryName = ignore, + }) { + return query.updateProperties(limit: limit, { + if (categoryName != ignore) 2: categoryName as String?, + }); + } +} + +extension CategoryQueryUpdate on IsarQuery { + _CategoryQueryUpdate get updateFirst => _CategoryQueryUpdateImpl(this, limit: 1); + + _CategoryQueryUpdate get updateAll => _CategoryQueryUpdateImpl(this); +} + +class _CategoryQueryBuilderUpdateImpl implements _CategoryQueryUpdate { + const _CategoryQueryBuilderUpdateImpl(this.query, {this.limit}); + + final QueryBuilder query; + final int? limit; + + @override + int call({ + Object? categoryName = ignore, + }) { + final q = query.build(); + try { + return q.updateProperties(limit: limit, { + if (categoryName != ignore) 2: categoryName as String?, + }); + } finally { + q.close(); + } + } +} + +extension CategoryQueryBuilderUpdate on QueryBuilder { + _CategoryQueryUpdate get updateFirst => _CategoryQueryBuilderUpdateImpl(this, limit: 1); + + _CategoryQueryUpdate get updateAll => _CategoryQueryBuilderUpdateImpl(this); +} + +extension CategoryQueryFilter on QueryBuilder { + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 1, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 1, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 1, + value: '', + ), + ); + }); + } + + QueryBuilder idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 1, + value: '', + ), + ); + }); + } + + QueryBuilder categoryNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 2, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 2, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 2, + value: '', + ), + ); + }); + } + + QueryBuilder categoryNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 2, + value: '', + ), + ); + }); + } +} + +extension CategoryQueryObject on QueryBuilder {} + +extension CategoryQuerySortBy on QueryBuilder { + QueryBuilder sortById({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 1, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 1, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByCategoryName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 2, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByCategoryNameDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 2, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } +} + +extension CategoryQuerySortThenBy on QueryBuilder { + QueryBuilder thenById({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(1, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(1, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByCategoryName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(2, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByCategoryNameDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(2, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } +} + +extension CategoryQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByCategoryName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(2, caseSensitive: caseSensitive); + }); + } +} + +extension CategoryQueryProperty1 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } +} + +extension CategoryQueryProperty2 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } +} + +extension CategoryQueryProperty3 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } +} diff --git a/lib/common/models/isar/diary.dart b/lib/common/models/isar/diary.dart new file mode 100644 index 0000000..e0fb083 --- /dev/null +++ b/lib/common/models/isar/diary.dart @@ -0,0 +1,80 @@ +import 'package:isar/isar.dart'; + +part 'diary.g.dart'; + +@collection +class Diary { + @Id() + late String id; + + //分类id + @Index() + String? categoryId; + + //标题 + String? title; + + //原始Delta格式内容 + late String content; + + //纯文本的内容,用于搜索以及字数统计 + late String contentText; + + //年月索引 + @Index() + String get yM => '${time.year.toString()}/${time.month.toString()}'; + + //年月日索引 + @Index() + String get yMd => '${time.year.toString()}/${time.month.toString()}/${time.day.toString()}'; + + //日期 + @Index() + late DateTime time; + + //是否显示,用于回收站 + @Index() + late bool show; + + //心情指数 + late double mood; + + //天气 + late List weather; + + //图片名称 + late List imageName; + + //音频名称 + late List audioName; + + //视频名称 + late List videoName; + + //标签列表 + late List tags; + + //封面颜色,如果有的话 + int? imageColor; + + //封面比例,如果有的话 + double? aspect; + + Diary({ + required this.id, + required this.categoryId, + required this.title, + required this.content, + required this.contentText, + required this.time, + required this.show, + required this.mood, + required this.weather, + required this.imageName, + required this.audioName, + required this.videoName, + required this.tags, + required this.imageColor, + required this.aspect, + }); +} diff --git a/lib/common/models/isar/diary.g.dart b/lib/common/models/isar/diary.g.dart new file mode 100644 index 0000000..3f991b8 --- /dev/null +++ b/lib/common/models/isar/diary.g.dart @@ -0,0 +1,3960 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'diary.dart'; + +// ************************************************************************** +// _IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, invalid_use_of_protected_member, lines_longer_than_80_chars, constant_identifier_names, avoid_js_rounded_ints, no_leading_underscores_for_local_identifiers, require_trailing_commas, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_in_if_null_operators, library_private_types_in_public_api, prefer_const_constructors +// ignore_for_file: type=lint + +extension GetDiaryCollection on Isar { + IsarCollection get diarys => this.collection(); +} + +const DiarySchema = IsarGeneratedSchema( + schema: IsarSchema( + name: 'Diary', + idName: 'id', + embedded: false, + properties: [ + IsarPropertySchema( + name: 'id', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'categoryId', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'title', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'content', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'contentText', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'time', + type: IsarType.dateTime, + ), + IsarPropertySchema( + name: 'show', + type: IsarType.bool, + ), + IsarPropertySchema( + name: 'mood', + type: IsarType.double, + ), + IsarPropertySchema( + name: 'weather', + type: IsarType.stringList, + ), + IsarPropertySchema( + name: 'imageName', + type: IsarType.stringList, + ), + IsarPropertySchema( + name: 'audioName', + type: IsarType.stringList, + ), + IsarPropertySchema( + name: 'videoName', + type: IsarType.stringList, + ), + IsarPropertySchema( + name: 'tags', + type: IsarType.stringList, + ), + IsarPropertySchema( + name: 'imageColor', + type: IsarType.long, + ), + IsarPropertySchema( + name: 'aspect', + type: IsarType.double, + ), + IsarPropertySchema( + name: 'yM', + type: IsarType.string, + ), + IsarPropertySchema( + name: 'yMd', + type: IsarType.string, + ), + ], + indexes: [ + IsarIndexSchema( + name: 'categoryId', + properties: [ + "categoryId", + ], + unique: false, + hash: false, + ), + IsarIndexSchema( + name: 'time', + properties: [ + "time", + ], + unique: false, + hash: false, + ), + IsarIndexSchema( + name: 'show', + properties: [ + "show", + ], + unique: false, + hash: false, + ), + IsarIndexSchema( + name: 'yM', + properties: [ + "yM", + ], + unique: false, + hash: false, + ), + IsarIndexSchema( + name: 'yMd', + properties: [ + "yMd", + ], + unique: false, + hash: false, + ), + ], + ), + converter: IsarObjectConverter( + serialize: serializeDiary, + deserialize: deserializeDiary, + deserializeProperty: deserializeDiaryProp, + ), + embeddedSchemas: [], +); + +@isarProtected +int serializeDiary(IsarWriter writer, Diary object) { + IsarCore.writeString(writer, 1, object.id); + { + final value = object.categoryId; + if (value == null) { + IsarCore.writeNull(writer, 2); + } else { + IsarCore.writeString(writer, 2, value); + } + } + { + final value = object.title; + if (value == null) { + IsarCore.writeNull(writer, 3); + } else { + IsarCore.writeString(writer, 3, value); + } + } + IsarCore.writeString(writer, 4, object.content); + IsarCore.writeString(writer, 5, object.contentText); + IsarCore.writeLong(writer, 6, object.time.toUtc().microsecondsSinceEpoch); + IsarCore.writeBool(writer, 7, object.show); + IsarCore.writeDouble(writer, 8, object.mood); + { + final list = object.weather; + final listWriter = IsarCore.beginList(writer, 9, list.length); + for (var i = 0; i < list.length; i++) { + IsarCore.writeString(listWriter, i, list[i]); + } + IsarCore.endList(writer, listWriter); + } + { + final list = object.imageName; + final listWriter = IsarCore.beginList(writer, 10, list.length); + for (var i = 0; i < list.length; i++) { + IsarCore.writeString(listWriter, i, list[i]); + } + IsarCore.endList(writer, listWriter); + } + { + final list = object.audioName; + final listWriter = IsarCore.beginList(writer, 11, list.length); + for (var i = 0; i < list.length; i++) { + IsarCore.writeString(listWriter, i, list[i]); + } + IsarCore.endList(writer, listWriter); + } + { + final list = object.videoName; + final listWriter = IsarCore.beginList(writer, 12, list.length); + for (var i = 0; i < list.length; i++) { + IsarCore.writeString(listWriter, i, list[i]); + } + IsarCore.endList(writer, listWriter); + } + { + final list = object.tags; + final listWriter = IsarCore.beginList(writer, 13, list.length); + for (var i = 0; i < list.length; i++) { + IsarCore.writeString(listWriter, i, list[i]); + } + IsarCore.endList(writer, listWriter); + } + IsarCore.writeLong(writer, 14, object.imageColor ?? -9223372036854775808); + IsarCore.writeDouble(writer, 15, object.aspect ?? double.nan); + IsarCore.writeString(writer, 16, object.yM); + IsarCore.writeString(writer, 17, object.yMd); + return Isar.fastHash(object.id); +} + +@isarProtected +Diary deserializeDiary(IsarReader reader) { + final String _id; + _id = IsarCore.readString(reader, 1) ?? ''; + final String? _categoryId; + _categoryId = IsarCore.readString(reader, 2); + final String? _title; + _title = IsarCore.readString(reader, 3); + final String _content; + _content = IsarCore.readString(reader, 4) ?? ''; + final String _contentText; + _contentText = IsarCore.readString(reader, 5) ?? ''; + final DateTime _time; + { + final value = IsarCore.readLong(reader, 6); + if (value == -9223372036854775808) { + _time = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true).toLocal(); + } else { + _time = DateTime.fromMicrosecondsSinceEpoch(value, isUtc: true).toLocal(); + } + } + final bool _show; + _show = IsarCore.readBool(reader, 7); + final double _mood; + _mood = IsarCore.readDouble(reader, 8); + final List _weather; + { + final length = IsarCore.readList(reader, 9, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + _weather = const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + _weather = list; + } + } + } + final List _imageName; + { + final length = IsarCore.readList(reader, 10, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + _imageName = const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + _imageName = list; + } + } + } + final List _audioName; + { + final length = IsarCore.readList(reader, 11, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + _audioName = const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + _audioName = list; + } + } + } + final List _videoName; + { + final length = IsarCore.readList(reader, 12, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + _videoName = const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + _videoName = list; + } + } + } + final List _tags; + { + final length = IsarCore.readList(reader, 13, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + _tags = const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + _tags = list; + } + } + } + final int? _imageColor; + { + final value = IsarCore.readLong(reader, 14); + if (value == -9223372036854775808) { + _imageColor = null; + } else { + _imageColor = value; + } + } + final double? _aspect; + { + final value = IsarCore.readDouble(reader, 15); + if (value.isNaN) { + _aspect = null; + } else { + _aspect = value; + } + } + final object = Diary( + id: _id, + categoryId: _categoryId, + title: _title, + content: _content, + contentText: _contentText, + time: _time, + show: _show, + mood: _mood, + weather: _weather, + imageName: _imageName, + audioName: _audioName, + videoName: _videoName, + tags: _tags, + imageColor: _imageColor, + aspect: _aspect, + ); + return object; +} + +@isarProtected +dynamic deserializeDiaryProp(IsarReader reader, int property) { + switch (property) { + case 1: + return IsarCore.readString(reader, 1) ?? ''; + case 2: + return IsarCore.readString(reader, 2); + case 3: + return IsarCore.readString(reader, 3); + case 4: + return IsarCore.readString(reader, 4) ?? ''; + case 5: + return IsarCore.readString(reader, 5) ?? ''; + case 6: + { + final value = IsarCore.readLong(reader, 6); + if (value == -9223372036854775808) { + return DateTime.fromMillisecondsSinceEpoch(0, isUtc: true).toLocal(); + } else { + return DateTime.fromMicrosecondsSinceEpoch(value, isUtc: true).toLocal(); + } + } + case 7: + return IsarCore.readBool(reader, 7); + case 8: + return IsarCore.readDouble(reader, 8); + case 9: + { + final length = IsarCore.readList(reader, 9, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + return const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + return list; + } + } + } + case 10: + { + final length = IsarCore.readList(reader, 10, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + return const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + return list; + } + } + } + case 11: + { + final length = IsarCore.readList(reader, 11, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + return const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + return list; + } + } + } + case 12: + { + final length = IsarCore.readList(reader, 12, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + return const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + return list; + } + } + } + case 13: + { + final length = IsarCore.readList(reader, 13, IsarCore.readerPtrPtr); + { + final reader = IsarCore.readerPtr; + if (reader.isNull) { + return const []; + } else { + final list = List.filled(length, '', growable: true); + for (var i = 0; i < length; i++) { + list[i] = IsarCore.readString(reader, i) ?? ''; + } + IsarCore.freeReader(reader); + return list; + } + } + } + case 14: + { + final value = IsarCore.readLong(reader, 14); + if (value == -9223372036854775808) { + return null; + } else { + return value; + } + } + case 15: + { + final value = IsarCore.readDouble(reader, 15); + if (value.isNaN) { + return null; + } else { + return value; + } + } + case 16: + return IsarCore.readString(reader, 16) ?? ''; + case 17: + return IsarCore.readString(reader, 17) ?? ''; + default: + throw ArgumentError('Unknown property: $property'); + } +} + +sealed class _DiaryUpdate { + bool call({ + required String id, + String? categoryId, + String? title, + String? content, + String? contentText, + DateTime? time, + bool? show, + double? mood, + int? imageColor, + double? aspect, + String? yM, + String? yMd, + }); +} + +class _DiaryUpdateImpl implements _DiaryUpdate { + const _DiaryUpdateImpl(this.collection); + + final IsarCollection collection; + + @override + bool call({ + required String id, + Object? categoryId = ignore, + Object? title = ignore, + Object? content = ignore, + Object? contentText = ignore, + Object? time = ignore, + Object? show = ignore, + Object? mood = ignore, + Object? imageColor = ignore, + Object? aspect = ignore, + Object? yM = ignore, + Object? yMd = ignore, + }) { + return collection.updateProperties([ + id + ], { + if (categoryId != ignore) 2: categoryId as String?, + if (title != ignore) 3: title as String?, + if (content != ignore) 4: content as String?, + if (contentText != ignore) 5: contentText as String?, + if (time != ignore) 6: time as DateTime?, + if (show != ignore) 7: show as bool?, + if (mood != ignore) 8: mood as double?, + if (imageColor != ignore) 14: imageColor as int?, + if (aspect != ignore) 15: aspect as double?, + if (yM != ignore) 16: yM as String?, + if (yMd != ignore) 17: yMd as String?, + }) > + 0; + } +} + +sealed class _DiaryUpdateAll { + int call({ + required List id, + String? categoryId, + String? title, + String? content, + String? contentText, + DateTime? time, + bool? show, + double? mood, + int? imageColor, + double? aspect, + String? yM, + String? yMd, + }); +} + +class _DiaryUpdateAllImpl implements _DiaryUpdateAll { + const _DiaryUpdateAllImpl(this.collection); + + final IsarCollection collection; + + @override + int call({ + required List id, + Object? categoryId = ignore, + Object? title = ignore, + Object? content = ignore, + Object? contentText = ignore, + Object? time = ignore, + Object? show = ignore, + Object? mood = ignore, + Object? imageColor = ignore, + Object? aspect = ignore, + Object? yM = ignore, + Object? yMd = ignore, + }) { + return collection.updateProperties(id, { + if (categoryId != ignore) 2: categoryId as String?, + if (title != ignore) 3: title as String?, + if (content != ignore) 4: content as String?, + if (contentText != ignore) 5: contentText as String?, + if (time != ignore) 6: time as DateTime?, + if (show != ignore) 7: show as bool?, + if (mood != ignore) 8: mood as double?, + if (imageColor != ignore) 14: imageColor as int?, + if (aspect != ignore) 15: aspect as double?, + if (yM != ignore) 16: yM as String?, + if (yMd != ignore) 17: yMd as String?, + }); + } +} + +extension DiaryUpdate on IsarCollection { + _DiaryUpdate get update => _DiaryUpdateImpl(this); + + _DiaryUpdateAll get updateAll => _DiaryUpdateAllImpl(this); +} + +sealed class _DiaryQueryUpdate { + int call({ + String? categoryId, + String? title, + String? content, + String? contentText, + DateTime? time, + bool? show, + double? mood, + int? imageColor, + double? aspect, + String? yM, + String? yMd, + }); +} + +class _DiaryQueryUpdateImpl implements _DiaryQueryUpdate { + const _DiaryQueryUpdateImpl(this.query, {this.limit}); + + final IsarQuery query; + final int? limit; + + @override + int call({ + Object? categoryId = ignore, + Object? title = ignore, + Object? content = ignore, + Object? contentText = ignore, + Object? time = ignore, + Object? show = ignore, + Object? mood = ignore, + Object? imageColor = ignore, + Object? aspect = ignore, + Object? yM = ignore, + Object? yMd = ignore, + }) { + return query.updateProperties(limit: limit, { + if (categoryId != ignore) 2: categoryId as String?, + if (title != ignore) 3: title as String?, + if (content != ignore) 4: content as String?, + if (contentText != ignore) 5: contentText as String?, + if (time != ignore) 6: time as DateTime?, + if (show != ignore) 7: show as bool?, + if (mood != ignore) 8: mood as double?, + if (imageColor != ignore) 14: imageColor as int?, + if (aspect != ignore) 15: aspect as double?, + if (yM != ignore) 16: yM as String?, + if (yMd != ignore) 17: yMd as String?, + }); + } +} + +extension DiaryQueryUpdate on IsarQuery { + _DiaryQueryUpdate get updateFirst => _DiaryQueryUpdateImpl(this, limit: 1); + + _DiaryQueryUpdate get updateAll => _DiaryQueryUpdateImpl(this); +} + +class _DiaryQueryBuilderUpdateImpl implements _DiaryQueryUpdate { + const _DiaryQueryBuilderUpdateImpl(this.query, {this.limit}); + + final QueryBuilder query; + final int? limit; + + @override + int call({ + Object? categoryId = ignore, + Object? title = ignore, + Object? content = ignore, + Object? contentText = ignore, + Object? time = ignore, + Object? show = ignore, + Object? mood = ignore, + Object? imageColor = ignore, + Object? aspect = ignore, + Object? yM = ignore, + Object? yMd = ignore, + }) { + final q = query.build(); + try { + return q.updateProperties(limit: limit, { + if (categoryId != ignore) 2: categoryId as String?, + if (title != ignore) 3: title as String?, + if (content != ignore) 4: content as String?, + if (contentText != ignore) 5: contentText as String?, + if (time != ignore) 6: time as DateTime?, + if (show != ignore) 7: show as bool?, + if (mood != ignore) 8: mood as double?, + if (imageColor != ignore) 14: imageColor as int?, + if (aspect != ignore) 15: aspect as double?, + if (yM != ignore) 16: yM as String?, + if (yMd != ignore) 17: yMd as String?, + }); + } finally { + q.close(); + } + } +} + +extension DiaryQueryBuilderUpdate on QueryBuilder { + _DiaryQueryUpdate get updateFirst => _DiaryQueryBuilderUpdateImpl(this, limit: 1); + + _DiaryQueryUpdate get updateAll => _DiaryQueryBuilderUpdateImpl(this); +} + +extension DiaryQueryFilter on QueryBuilder { + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 1, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 1, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 1, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 1, + value: '', + ), + ); + }); + } + + QueryBuilder idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 1, + value: '', + ), + ); + }); + } + + QueryBuilder categoryIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const IsNullCondition(property: 2)); + }); + } + + QueryBuilder categoryIdIsNotNull() { + return QueryBuilder.apply(not(), (query) { + return query.addFilterCondition(const IsNullCondition(property: 2)); + }); + } + + QueryBuilder categoryIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdGreaterThan( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdGreaterThanOrEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdLessThan( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdLessThanOrEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdBetween( + String? lower, + String? upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 2, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 2, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 2, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder categoryIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 2, + value: '', + ), + ); + }); + } + + QueryBuilder categoryIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 2, + value: '', + ), + ); + }); + } + + QueryBuilder titleIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const IsNullCondition(property: 3)); + }); + } + + QueryBuilder titleIsNotNull() { + return QueryBuilder.apply(not(), (query) { + return query.addFilterCondition(const IsNullCondition(property: 3)); + }); + } + + QueryBuilder titleEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleGreaterThan( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleGreaterThanOrEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleLessThan( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleLessThanOrEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleBetween( + String? lower, + String? upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 3, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 3, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 3, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder titleIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 3, + value: '', + ), + ); + }); + } + + QueryBuilder titleIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 3, + value: '', + ), + ); + }); + } + + QueryBuilder contentEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 4, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 4, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 4, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 4, + value: '', + ), + ); + }); + } + + QueryBuilder contentIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 4, + value: '', + ), + ); + }); + } + + QueryBuilder contentTextEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 5, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 5, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 5, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder contentTextIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 5, + value: '', + ), + ); + }); + } + + QueryBuilder contentTextIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 5, + value: '', + ), + ); + }); + } + + QueryBuilder timeEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 6, + value: value, + ), + ); + }); + } + + QueryBuilder timeGreaterThan( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 6, + value: value, + ), + ); + }); + } + + QueryBuilder timeGreaterThanOrEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 6, + value: value, + ), + ); + }); + } + + QueryBuilder timeLessThan( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 6, + value: value, + ), + ); + }); + } + + QueryBuilder timeLessThanOrEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 6, + value: value, + ), + ); + }); + } + + QueryBuilder timeBetween( + DateTime lower, + DateTime upper, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 6, + lower: lower, + upper: upper, + ), + ); + }); + } + + QueryBuilder showEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 7, + value: value, + ), + ); + }); + } + + QueryBuilder moodEqualTo( + double value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 8, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder moodGreaterThan( + double value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 8, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder moodGreaterThanOrEqualTo( + double value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 8, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder moodLessThan( + double value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 8, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder moodLessThanOrEqualTo( + double value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 8, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder moodBetween( + double lower, + double upper, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 8, + lower: lower, + upper: upper, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder weatherElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 9, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 9, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 9, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder weatherElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 9, + value: '', + ), + ); + }); + } + + QueryBuilder weatherElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 9, + value: '', + ), + ); + }); + } + + QueryBuilder weatherIsEmpty() { + return not().weatherIsNotEmpty(); + } + + QueryBuilder weatherIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterOrEqualCondition(property: 9, value: null), + ); + }); + } + + QueryBuilder imageNameElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 10, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 10, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 10, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder imageNameElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 10, + value: '', + ), + ); + }); + } + + QueryBuilder imageNameElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 10, + value: '', + ), + ); + }); + } + + QueryBuilder imageNameIsEmpty() { + return not().imageNameIsNotEmpty(); + } + + QueryBuilder imageNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterOrEqualCondition(property: 10, value: null), + ); + }); + } + + QueryBuilder audioNameElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 11, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 11, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 11, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder audioNameElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 11, + value: '', + ), + ); + }); + } + + QueryBuilder audioNameElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 11, + value: '', + ), + ); + }); + } + + QueryBuilder audioNameIsEmpty() { + return not().audioNameIsNotEmpty(); + } + + QueryBuilder audioNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterOrEqualCondition(property: 11, value: null), + ); + }); + } + + QueryBuilder videoNameElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 12, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 12, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 12, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder videoNameElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 12, + value: '', + ), + ); + }); + } + + QueryBuilder videoNameElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 12, + value: '', + ), + ); + }); + } + + QueryBuilder videoNameIsEmpty() { + return not().videoNameIsNotEmpty(); + } + + QueryBuilder videoNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterOrEqualCondition(property: 12, value: null), + ); + }); + } + + QueryBuilder tagsElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 13, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 13, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 13, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder tagsElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 13, + value: '', + ), + ); + }); + } + + QueryBuilder tagsElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 13, + value: '', + ), + ); + }); + } + + QueryBuilder tagsIsEmpty() { + return not().tagsIsNotEmpty(); + } + + QueryBuilder tagsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterOrEqualCondition(property: 13, value: null), + ); + }); + } + + QueryBuilder imageColorIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const IsNullCondition(property: 14)); + }); + } + + QueryBuilder imageColorIsNotNull() { + return QueryBuilder.apply(not(), (query) { + return query.addFilterCondition(const IsNullCondition(property: 14)); + }); + } + + QueryBuilder imageColorEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 14, + value: value, + ), + ); + }); + } + + QueryBuilder imageColorGreaterThan( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 14, + value: value, + ), + ); + }); + } + + QueryBuilder imageColorGreaterThanOrEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 14, + value: value, + ), + ); + }); + } + + QueryBuilder imageColorLessThan( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 14, + value: value, + ), + ); + }); + } + + QueryBuilder imageColorLessThanOrEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 14, + value: value, + ), + ); + }); + } + + QueryBuilder imageColorBetween( + int? lower, + int? upper, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 14, + lower: lower, + upper: upper, + ), + ); + }); + } + + QueryBuilder aspectIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const IsNullCondition(property: 15)); + }); + } + + QueryBuilder aspectIsNotNull() { + return QueryBuilder.apply(not(), (query) { + return query.addFilterCondition(const IsNullCondition(property: 15)); + }); + } + + QueryBuilder aspectEqualTo( + double? value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 15, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder aspectGreaterThan( + double? value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 15, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder aspectGreaterThanOrEqualTo( + double? value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 15, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder aspectLessThan( + double? value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 15, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder aspectLessThanOrEqualTo( + double? value, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 15, + value: value, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder aspectBetween( + double? lower, + double? upper, { + double epsilon = Filter.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 15, + lower: lower, + upper: upper, + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder yMEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 16, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 16, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 16, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 16, + value: '', + ), + ); + }); + } + + QueryBuilder yMIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 16, + value: '', + ), + ); + }); + } + + QueryBuilder yMdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EqualCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdGreaterThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdGreaterThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + GreaterOrEqualCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdLessThan( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdLessThanOrEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + LessOrEqualCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdBetween( + String lower, + String upper, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + BetweenCondition( + property: 17, + lower: lower, + upper: upper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + StartsWithCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + EndsWithCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + ContainsCondition( + property: 17, + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + MatchesCondition( + property: 17, + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder yMdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const EqualCondition( + property: 17, + value: '', + ), + ); + }); + } + + QueryBuilder yMdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const GreaterCondition( + property: 17, + value: '', + ), + ); + }); + } +} + +extension DiaryQueryObject on QueryBuilder {} + +extension DiaryQuerySortBy on QueryBuilder { + QueryBuilder sortById({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 1, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 1, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByCategoryId({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 2, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByCategoryIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 2, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByTitle({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 3, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByTitleDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 3, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByContent({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 4, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByContentDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 4, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByContentText({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 5, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByContentTextDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 5, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(6); + }); + } + + QueryBuilder sortByTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(6, sort: Sort.desc); + }); + } + + QueryBuilder sortByShow() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(7); + }); + } + + QueryBuilder sortByShowDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(7, sort: Sort.desc); + }); + } + + QueryBuilder sortByMood() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(8); + }); + } + + QueryBuilder sortByMoodDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(8, sort: Sort.desc); + }); + } + + QueryBuilder sortByImageColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(14); + }); + } + + QueryBuilder sortByImageColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(14, sort: Sort.desc); + }); + } + + QueryBuilder sortByAspect() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(15); + }); + } + + QueryBuilder sortByAspectDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(15, sort: Sort.desc); + }); + } + + QueryBuilder sortByYM({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 16, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByYMDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 16, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByYMd({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 17, + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder sortByYMdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + 17, + sort: Sort.desc, + caseSensitive: caseSensitive, + ); + }); + } +} + +extension DiaryQuerySortThenBy on QueryBuilder { + QueryBuilder thenById({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(1, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(1, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByCategoryId({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(2, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByCategoryIdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(2, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByTitle({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(3, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByTitleDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(3, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByContent({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(4, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByContentDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(4, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByContentText({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(5, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByContentTextDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(5, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(6); + }); + } + + QueryBuilder thenByTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(6, sort: Sort.desc); + }); + } + + QueryBuilder thenByShow() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(7); + }); + } + + QueryBuilder thenByShowDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(7, sort: Sort.desc); + }); + } + + QueryBuilder thenByMood() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(8); + }); + } + + QueryBuilder thenByMoodDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(8, sort: Sort.desc); + }); + } + + QueryBuilder thenByImageColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(14); + }); + } + + QueryBuilder thenByImageColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(14, sort: Sort.desc); + }); + } + + QueryBuilder thenByAspect() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(15); + }); + } + + QueryBuilder thenByAspectDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(15, sort: Sort.desc); + }); + } + + QueryBuilder thenByYM({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(16, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByYMDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(16, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByYMd({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(17, caseSensitive: caseSensitive); + }); + } + + QueryBuilder thenByYMdDesc({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(17, sort: Sort.desc, caseSensitive: caseSensitive); + }); + } +} + +extension DiaryQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByCategoryId({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(2, caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTitle({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(3, caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByContent({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(4, caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByContentText({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(5, caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(6); + }); + } + + QueryBuilder distinctByShow() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(7); + }); + } + + QueryBuilder distinctByMood() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(8); + }); + } + + QueryBuilder distinctByWeather() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(9); + }); + } + + QueryBuilder distinctByImageName() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(10); + }); + } + + QueryBuilder distinctByAudioName() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(11); + }); + } + + QueryBuilder distinctByVideoName() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(12); + }); + } + + QueryBuilder distinctByTags() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(13); + }); + } + + QueryBuilder distinctByImageColor() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(14); + }); + } + + QueryBuilder distinctByAspect() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(15); + }); + } + + QueryBuilder distinctByYM({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(16, caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByYMd({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(17, caseSensitive: caseSensitive); + }); + } +} + +extension DiaryQueryProperty1 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } + + QueryBuilder titleProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(3); + }); + } + + QueryBuilder contentProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(4); + }); + } + + QueryBuilder contentTextProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(5); + }); + } + + QueryBuilder timeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(6); + }); + } + + QueryBuilder showProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(7); + }); + } + + QueryBuilder moodProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(8); + }); + } + + QueryBuilder, QAfterProperty> weatherProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(9); + }); + } + + QueryBuilder, QAfterProperty> imageNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(10); + }); + } + + QueryBuilder, QAfterProperty> audioNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(11); + }); + } + + QueryBuilder, QAfterProperty> videoNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(12); + }); + } + + QueryBuilder, QAfterProperty> tagsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(13); + }); + } + + QueryBuilder imageColorProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(14); + }); + } + + QueryBuilder aspectProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(15); + }); + } + + QueryBuilder yMProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(16); + }); + } + + QueryBuilder yMdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(17); + }); + } +} + +extension DiaryQueryProperty2 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } + + QueryBuilder titleProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(3); + }); + } + + QueryBuilder contentProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(4); + }); + } + + QueryBuilder contentTextProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(5); + }); + } + + QueryBuilder timeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(6); + }); + } + + QueryBuilder showProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(7); + }); + } + + QueryBuilder moodProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(8); + }); + } + + QueryBuilder), QAfterProperty> weatherProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(9); + }); + } + + QueryBuilder), QAfterProperty> imageNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(10); + }); + } + + QueryBuilder), QAfterProperty> audioNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(11); + }); + } + + QueryBuilder), QAfterProperty> videoNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(12); + }); + } + + QueryBuilder), QAfterProperty> tagsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(13); + }); + } + + QueryBuilder imageColorProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(14); + }); + } + + QueryBuilder aspectProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(15); + }); + } + + QueryBuilder yMProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(16); + }); + } + + QueryBuilder yMdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(17); + }); + } +} + +extension DiaryQueryProperty3 on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(1); + }); + } + + QueryBuilder categoryIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(2); + }); + } + + QueryBuilder titleProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(3); + }); + } + + QueryBuilder contentProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(4); + }); + } + + QueryBuilder contentTextProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(5); + }); + } + + QueryBuilder timeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(6); + }); + } + + QueryBuilder showProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(7); + }); + } + + QueryBuilder moodProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(8); + }); + } + + QueryBuilder), QOperations> weatherProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(9); + }); + } + + QueryBuilder), QOperations> imageNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(10); + }); + } + + QueryBuilder), QOperations> audioNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(11); + }); + } + + QueryBuilder), QOperations> videoNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(12); + }); + } + + QueryBuilder), QOperations> tagsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(13); + }); + } + + QueryBuilder imageColorProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(14); + }); + } + + QueryBuilder aspectProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(15); + }); + } + + QueryBuilder yMProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(16); + }); + } + + QueryBuilder yMdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addProperty(17); + }); + } +} diff --git a/lib/common/models/shiply.dart b/lib/common/models/shiply.dart new file mode 100644 index 0000000..ecfc3c7 --- /dev/null +++ b/lib/common/models/shiply.dart @@ -0,0 +1,161 @@ +class ShiplyResponse { + ApkBasicInfo? apkBasicInfo; + ClientInfo? clientInfo; + Extra? extra; + int? grayType; + String? newFeature; + int? popInterval; + int? popTimes; + int? publishTime; + int? receiveMoment; + int? remindType; + int? status; + String? tacticsId; + String? title; + int? undisturbedDuration; + int? updateStrategy; + int? updateTime; + + ShiplyResponse( + {this.apkBasicInfo, + this.clientInfo, + this.extra, + this.grayType, + this.newFeature, + this.popInterval, + this.popTimes, + this.publishTime, + this.receiveMoment, + this.remindType, + this.status, + this.tacticsId, + this.title, + this.undisturbedDuration, + this.updateStrategy, + this.updateTime}); + + ShiplyResponse.fromJson(Map json) { + apkBasicInfo = json["apkBasicInfo"] == null ? null : ApkBasicInfo.fromJson(json["apkBasicInfo"]); + clientInfo = json["clientInfo"] == null ? null : ClientInfo.fromJson(json["clientInfo"]); + extra = json["extra"] == null ? null : Extra.fromJson(json["extra"]); + grayType = json["grayType"]; + newFeature = json["newFeature"]; + popInterval = json["popInterval"]; + popTimes = json["popTimes"]; + publishTime = json["publishTime"]; + receiveMoment = json["receiveMoment"]; + remindType = json["remindType"]; + status = json["status"]; + tacticsId = json["tacticsId"]; + title = json["title"]; + undisturbedDuration = json["undisturbedDuration"]; + updateStrategy = json["updateStrategy"]; + updateTime = json["updateTime"]; + } + + Map toJson() { + final Map data = {}; + if (apkBasicInfo != null) { + data["apkBasicInfo"] = apkBasicInfo?.toJson(); + } + if (clientInfo != null) { + data["clientInfo"] = clientInfo?.toJson(); + } + if (extra != null) { + data["extra"] = extra?.toJson(); + } + data["grayType"] = grayType; + data["newFeature"] = newFeature; + data["popInterval"] = popInterval; + data["popTimes"] = popTimes; + data["publishTime"] = publishTime; + data["receiveMoment"] = receiveMoment; + data["remindType"] = remindType; + data["status"] = status; + data["tacticsId"] = tacticsId; + data["title"] = title; + data["undisturbedDuration"] = undisturbedDuration; + data["updateStrategy"] = updateStrategy; + data["updateTime"] = updateTime; + return data; + } +} + +class Extra { + Extra(); + + Extra.fromJson(Map json); + + Map toJson() { + final Map data = {}; + + return data; + } +} + +class ClientInfo { + String? description; + String? title; + int? type; + + ClientInfo({this.description, this.title, this.type}); + + ClientInfo.fromJson(Map json) { + description = json["description"]; + title = json["title"]; + type = json["type"]; + } + + Map toJson() { + final Map data = {}; + data["description"] = description; + data["title"] = title; + data["type"] = type; + return data; + } +} + +class ApkBasicInfo { + String? md5; + int? pkgSize; + int? buildNo; + String? bundleId; + String? downloadUrl; + String? pkgName; + int? versionCode; + String? version; + + ApkBasicInfo( + {this.md5, + this.pkgSize, + this.buildNo, + this.bundleId, + this.downloadUrl, + this.pkgName, + this.versionCode, + this.version}); + + ApkBasicInfo.fromJson(Map json) { + md5 = json["md5"]; + pkgSize = json["pkgSize"]; + buildNo = json["buildNo"]; + bundleId = json["bundleId"]; + downloadUrl = json["downloadUrl"]; + pkgName = json["pkgName"]; + versionCode = json["versionCode"]; + version = json["version"]; + } + + Map toJson() { + final Map data = {}; + data["md5"] = md5; + data["pkgSize"] = pkgSize; + data["buildNo"] = buildNo; + data["bundleId"] = bundleId; + data["downloadUrl"] = downloadUrl; + data["pkgName"] = pkgName; + data["versionCode"] = versionCode; + data["version"] = version; + return data; + } +} diff --git a/lib/common/models/weather.dart b/lib/common/models/weather.dart new file mode 100644 index 0000000..fb59eee --- /dev/null +++ b/lib/common/models/weather.dart @@ -0,0 +1,127 @@ +class WeatherResponse { + String? code; + String? updateTime; + String? fxLink; + Now? now; + Refer? refer; + + WeatherResponse({this.code, this.updateTime, this.fxLink, this.now, this.refer}); + + WeatherResponse.fromJson(Map json) { + code = json["code"]; + updateTime = json["updateTime"]; + fxLink = json["fxLink"]; + now = json["now"] == null ? null : Now.fromJson(json["now"]); + refer = json["refer"] == null ? null : Refer.fromJson(json["refer"]); + } + + Map toJson() { + final Map data = {}; + data["code"] = code; + data["updateTime"] = updateTime; + data["fxLink"] = fxLink; + if (now != null) { + data["now"] = now?.toJson(); + } + if (refer != null) { + data["refer"] = refer?.toJson(); + } + return data; + } +} + +class Refer { + List? sources; + List? license; + + Refer({this.sources, this.license}); + + Refer.fromJson(Map json) { + sources = json["sources"] == null ? null : List.from(json["sources"]); + license = json["license"] == null ? null : List.from(json["license"]); + } + + Map toJson() { + final Map data = {}; + if (sources != null) { + data["sources"] = sources; + } + if (license != null) { + data["license"] = license; + } + return data; + } +} + +class Now { + String? obsTime; + String? temp; + String? feelsLike; + String? icon; + String? text; + String? wind360; + String? windDir; + String? windScale; + String? windSpeed; + String? humidity; + String? precip; + String? pressure; + String? vis; + String? cloud; + String? dew; + + Now( + {this.obsTime, + this.temp, + this.feelsLike, + this.icon, + this.text, + this.wind360, + this.windDir, + this.windScale, + this.windSpeed, + this.humidity, + this.precip, + this.pressure, + this.vis, + this.cloud, + this.dew}); + + Now.fromJson(Map json) { + obsTime = json["obsTime"]; + temp = json["temp"]; + feelsLike = json["feelsLike"]; + icon = json["icon"]; + text = json["text"]; + wind360 = json["wind360"]; + windDir = json["windDir"]; + windScale = json["windScale"]; + windSpeed = json["windSpeed"]; + humidity = json["humidity"]; + precip = json["precip"]; + pressure = json["pressure"]; + vis = json["vis"]; + cloud = json["cloud"]; + dew = json["dew"]; + } + + Map toJson() { + final Map data = {}; + data["obsTime"] = obsTime; + data["temp"] = temp; + data["feelsLike"] = feelsLike; + data["icon"] = icon; + data["text"] = text; + data["wind360"] = wind360; + data["windDir"] = windDir; + data["windScale"] = windScale; + data["windSpeed"] = windSpeed; + data["humidity"] = humidity; + data["precip"] = precip; + data["pressure"] = pressure; + data["vis"] = vis; + data["cloud"] = cloud; + data["dew"] = dew; + return data; + } +} diff --git a/lib/common/values/colors.dart b/lib/common/values/colors.dart new file mode 100644 index 0000000..d8d4176 --- /dev/null +++ b/lib/common/values/colors.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class AppColor { + static List colorList = [ + //百草霜 + const Color(0xFF363532), + //竹月 + const Color(0xFF5E90B8), + //绿琉璃 + const Color(0xFF4F7E57), + //槿 + const Color(0xFF806D9E), + //十样锦 + const Color(0xFFFCB1AA) + ]; +} diff --git a/lib/common/values/icons.dart b/lib/common/values/icons.dart new file mode 100644 index 0000000..b30c4c0 --- /dev/null +++ b/lib/common/values/icons.dart @@ -0,0 +1,424 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class WeatherIcon { + static Map map = { + "100": const IconData(61697, fontFamily: 'qweather'), + "101": const IconData(61698, fontFamily: 'qweather'), + "102": const IconData(61699, fontFamily: 'qweather'), + "103": const IconData(61700, fontFamily: 'qweather'), + "104": const IconData(61701, fontFamily: 'qweather'), + "150": const IconData(61702, fontFamily: 'qweather'), + "151": const IconData(61703, fontFamily: 'qweather'), + "152": const IconData(61704, fontFamily: 'qweather'), + "153": const IconData(61705, fontFamily: 'qweather'), + "300": const IconData(61706, fontFamily: 'qweather'), + "301": const IconData(61707, fontFamily: 'qweather'), + "302": const IconData(61708, fontFamily: 'qweather'), + "303": const IconData(61709, fontFamily: 'qweather'), + "304": const IconData(61710, fontFamily: 'qweather'), + "305": const IconData(61711, fontFamily: 'qweather'), + "306": const IconData(61712, fontFamily: 'qweather'), + "307": const IconData(61713, fontFamily: 'qweather'), + "308": const IconData(61714, fontFamily: 'qweather'), + "309": const IconData(61715, fontFamily: 'qweather'), + "310": const IconData(61716, fontFamily: 'qweather'), + "311": const IconData(61717, fontFamily: 'qweather'), + "312": const IconData(61718, fontFamily: 'qweather'), + "313": const IconData(61719, fontFamily: 'qweather'), + "314": const IconData(61720, fontFamily: 'qweather'), + "315": const IconData(61721, fontFamily: 'qweather'), + "316": const IconData(61722, fontFamily: 'qweather'), + "317": const IconData(61723, fontFamily: 'qweather'), + "318": const IconData(61724, fontFamily: 'qweather'), + "350": const IconData(61725, fontFamily: 'qweather'), + "351": const IconData(61726, fontFamily: 'qweather'), + "399": const IconData(61727, fontFamily: 'qweather'), + "400": const IconData(61728, fontFamily: 'qweather'), + "401": const IconData(61729, fontFamily: 'qweather'), + "402": const IconData(61730, fontFamily: 'qweather'), + "403": const IconData(61731, fontFamily: 'qweather'), + "404": const IconData(61732, fontFamily: 'qweather'), + "405": const IconData(61733, fontFamily: 'qweather'), + "406": const IconData(61734, fontFamily: 'qweather'), + "407": const IconData(61735, fontFamily: 'qweather'), + "408": const IconData(61736, fontFamily: 'qweather'), + "409": const IconData(61737, fontFamily: 'qweather'), + "410": const IconData(61738, fontFamily: 'qweather'), + "456": const IconData(61739, fontFamily: 'qweather'), + "457": const IconData(61740, fontFamily: 'qweather'), + "499": const IconData(61741, fontFamily: 'qweather'), + "500": const IconData(61742, fontFamily: 'qweather'), + "501": const IconData(61743, fontFamily: 'qweather'), + "502": const IconData(61744, fontFamily: 'qweather'), + "503": const IconData(61745, fontFamily: 'qweather'), + "504": const IconData(61746, fontFamily: 'qweather'), + "507": const IconData(61747, fontFamily: 'qweather'), + "508": const IconData(61748, fontFamily: 'qweather'), + "509": const IconData(61749, fontFamily: 'qweather'), + "510": const IconData(61750, fontFamily: 'qweather'), + "511": const IconData(61751, fontFamily: 'qweather'), + "512": const IconData(61752, fontFamily: 'qweather'), + "513": const IconData(61753, fontFamily: 'qweather'), + "514": const IconData(61754, fontFamily: 'qweather'), + "515": const IconData(61755, fontFamily: 'qweather'), + "800": const IconData(61756, fontFamily: 'qweather'), + "801": const IconData(61757, fontFamily: 'qweather'), + "802": const IconData(61758, fontFamily: 'qweather'), + "803": const IconData(61759, fontFamily: 'qweather'), + "804": const IconData(61760, fontFamily: 'qweather'), + "805": const IconData(61761, fontFamily: 'qweather'), + "806": const IconData(61762, fontFamily: 'qweather'), + "807": const IconData(61763, fontFamily: 'qweather'), + "900": const IconData(61764, fontFamily: 'qweather'), + "901": const IconData(61765, fontFamily: 'qweather'), + "999": const IconData(61766, fontFamily: 'qweather'), + "1001": const IconData(61767, fontFamily: 'qweather'), + "1002": const IconData(61768, fontFamily: 'qweather'), + "1003": const IconData(61769, fontFamily: 'qweather'), + "1004": const IconData(61770, fontFamily: 'qweather'), + "1005": const IconData(61771, fontFamily: 'qweather'), + "1006": const IconData(61772, fontFamily: 'qweather'), + "1007": const IconData(61773, fontFamily: 'qweather'), + "1008": const IconData(61774, fontFamily: 'qweather'), + "1009": const IconData(61775, fontFamily: 'qweather'), + "1010": const IconData(61776, fontFamily: 'qweather'), + "1011": const IconData(61777, fontFamily: 'qweather'), + "1012": const IconData(61778, fontFamily: 'qweather'), + "1013": const IconData(61779, fontFamily: 'qweather'), + "1014": const IconData(61780, fontFamily: 'qweather'), + "1015": const IconData(61781, fontFamily: 'qweather'), + "1016": const IconData(61782, fontFamily: 'qweather'), + "1017": const IconData(61783, fontFamily: 'qweather'), + "1018": const IconData(61784, fontFamily: 'qweather'), + "1019": const IconData(61785, fontFamily: 'qweather'), + "1020": const IconData(61786, fontFamily: 'qweather'), + "1021": const IconData(61787, fontFamily: 'qweather'), + "1022": const IconData(61788, fontFamily: 'qweather'), + "1023": const IconData(61789, fontFamily: 'qweather'), + "1024": const IconData(61790, fontFamily: 'qweather'), + "1025": const IconData(61791, fontFamily: 'qweather'), + "1026": const IconData(61792, fontFamily: 'qweather'), + "1027": const IconData(61793, fontFamily: 'qweather'), + "1028": const IconData(61794, fontFamily: 'qweather'), + "1029": const IconData(61795, fontFamily: 'qweather'), + "1030": const IconData(61796, fontFamily: 'qweather'), + "1031": const IconData(61797, fontFamily: 'qweather'), + "1032": const IconData(61798, fontFamily: 'qweather'), + "1033": const IconData(61799, fontFamily: 'qweather'), + "1034": const IconData(61800, fontFamily: 'qweather'), + "1035": const IconData(61801, fontFamily: 'qweather'), + "1036": const IconData(61802, fontFamily: 'qweather'), + "1037": const IconData(61803, fontFamily: 'qweather'), + "1038": const IconData(61804, fontFamily: 'qweather'), + "1039": const IconData(61805, fontFamily: 'qweather'), + "1040": const IconData(61806, fontFamily: 'qweather'), + "1041": const IconData(61807, fontFamily: 'qweather'), + "1042": const IconData(61808, fontFamily: 'qweather'), + "1043": const IconData(61809, fontFamily: 'qweather'), + "1044": const IconData(61810, fontFamily: 'qweather'), + "1045": const IconData(61811, fontFamily: 'qweather'), + "1046": const IconData(61812, fontFamily: 'qweather'), + "1047": const IconData(61813, fontFamily: 'qweather'), + "1048": const IconData(61814, fontFamily: 'qweather'), + "1049": const IconData(61815, fontFamily: 'qweather'), + "1050": const IconData(61816, fontFamily: 'qweather'), + "1051": const IconData(61817, fontFamily: 'qweather'), + "1052": const IconData(61818, fontFamily: 'qweather'), + "1053": const IconData(61819, fontFamily: 'qweather'), + "1054": const IconData(61820, fontFamily: 'qweather'), + "1055": const IconData(61821, fontFamily: 'qweather'), + "1056": const IconData(61822, fontFamily: 'qweather'), + "1057": const IconData(61823, fontFamily: 'qweather'), + "1058": const IconData(61824, fontFamily: 'qweather'), + "1059": const IconData(61825, fontFamily: 'qweather'), + "1060": const IconData(61826, fontFamily: 'qweather'), + "1061": const IconData(61827, fontFamily: 'qweather'), + "1062": const IconData(61828, fontFamily: 'qweather'), + "1063": const IconData(61829, fontFamily: 'qweather'), + "1064": const IconData(61830, fontFamily: 'qweather'), + "1065": const IconData(61831, fontFamily: 'qweather'), + "1066": const IconData(61832, fontFamily: 'qweather'), + "1067": const IconData(61833, fontFamily: 'qweather'), + "1068": const IconData(61834, fontFamily: 'qweather'), + "1069": const IconData(61835, fontFamily: 'qweather'), + "1071": const IconData(61836, fontFamily: 'qweather'), + "1072": const IconData(61837, fontFamily: 'qweather'), + "1073": const IconData(61838, fontFamily: 'qweather'), + "1074": const IconData(61839, fontFamily: 'qweather'), + "1075": const IconData(61840, fontFamily: 'qweather'), + "1076": const IconData(61841, fontFamily: 'qweather'), + "1077": const IconData(61842, fontFamily: 'qweather'), + "1078": const IconData(61843, fontFamily: 'qweather'), + "1079": const IconData(61844, fontFamily: 'qweather'), + "1080": const IconData(61845, fontFamily: 'qweather'), + "1081": const IconData(61846, fontFamily: 'qweather'), + "1082": const IconData(61847, fontFamily: 'qweather'), + "1084": const IconData(61848, fontFamily: 'qweather'), + "1085": const IconData(61849, fontFamily: 'qweather'), + "1086": const IconData(61850, fontFamily: 'qweather'), + "1087": const IconData(61851, fontFamily: 'qweather'), + "1088": const IconData(61852, fontFamily: 'qweather'), + "1089": const IconData(61853, fontFamily: 'qweather'), + "1201": const IconData(62149, fontFamily: 'qweather'), + "1202": const IconData(62150, fontFamily: 'qweather'), + "1203": const IconData(62151, fontFamily: 'qweather'), + "1204": const IconData(62152, fontFamily: 'qweather'), + "1205": const IconData(62153, fontFamily: 'qweather'), + "1206": const IconData(62154, fontFamily: 'qweather'), + "1207": const IconData(62155, fontFamily: 'qweather'), + "1208": const IconData(62156, fontFamily: 'qweather'), + "1209": const IconData(62157, fontFamily: 'qweather'), + "1210": const IconData(62158, fontFamily: 'qweather'), + "1211": const IconData(62159, fontFamily: 'qweather'), + "1212": const IconData(62160, fontFamily: 'qweather'), + "1213": const IconData(62161, fontFamily: 'qweather'), + "1214": const IconData(62162, fontFamily: 'qweather'), + "1215": const IconData(62163, fontFamily: 'qweather'), + "1216": const IconData(62164, fontFamily: 'qweather'), + "1217": const IconData(62165, fontFamily: 'qweather'), + "1218": const IconData(62166, fontFamily: 'qweather'), + "1219": const IconData(62167, fontFamily: 'qweather'), + "1221": const IconData(62168, fontFamily: 'qweather'), + "1241": const IconData(62169, fontFamily: 'qweather'), + "1242": const IconData(62170, fontFamily: 'qweather'), + "1243": const IconData(62171, fontFamily: 'qweather'), + "1244": const IconData(62172, fontFamily: 'qweather'), + "1245": const IconData(62173, fontFamily: 'qweather'), + "1246": const IconData(62174, fontFamily: 'qweather'), + "1247": const IconData(62175, fontFamily: 'qweather'), + "1248": const IconData(62176, fontFamily: 'qweather'), + "1249": const IconData(62177, fontFamily: 'qweather'), + "1250": const IconData(62178, fontFamily: 'qweather'), + "1251": const IconData(62179, fontFamily: 'qweather'), + "1271": const IconData(62198, fontFamily: 'qweather'), + "1272": const IconData(62199, fontFamily: 'qweather'), + "1273": const IconData(62200, fontFamily: 'qweather'), + "1274": const IconData(62201, fontFamily: 'qweather'), + "1601": const IconData(61857, fontFamily: 'qweather'), + "1602": const IconData(61858, fontFamily: 'qweather'), + "1603": const IconData(61859, fontFamily: 'qweather'), + "1604": const IconData(61860, fontFamily: 'qweather'), + "1605": const IconData(61861, fontFamily: 'qweather'), + "1606": const IconData(61862, fontFamily: 'qweather'), + "1607": const IconData(61863, fontFamily: 'qweather'), + "1608": const IconData(61964, fontFamily: 'qweather'), + "1609": const IconData(61965, fontFamily: 'qweather'), + "1610": const IconData(61966, fontFamily: 'qweather'), + "1701": const IconData(61864, fontFamily: 'qweather'), + "1702": const IconData(61865, fontFamily: 'qweather'), + "1703": const IconData(61866, fontFamily: 'qweather'), + "1801": const IconData(61967, fontFamily: 'qweather'), + "1802": const IconData(61968, fontFamily: 'qweather'), + "1803": const IconData(61969, fontFamily: 'qweather'), + "1804": const IconData(61970, fontFamily: 'qweather'), + "1805": const IconData(61971, fontFamily: 'qweather'), + "2001": const IconData(61867, fontFamily: 'qweather'), + "2002": const IconData(61868, fontFamily: 'qweather'), + "2003": const IconData(61869, fontFamily: 'qweather'), + "2004": const IconData(61870, fontFamily: 'qweather'), + "2005": const IconData(61871, fontFamily: 'qweather'), + "2006": const IconData(61872, fontFamily: 'qweather'), + "2007": const IconData(61873, fontFamily: 'qweather'), + "2029": const IconData(61972, fontFamily: 'qweather'), + "2030": const IconData(61973, fontFamily: 'qweather'), + "2031": const IconData(61974, fontFamily: 'qweather'), + "2032": const IconData(61975, fontFamily: 'qweather'), + "2033": const IconData(61976, fontFamily: 'qweather'), + "2050": const IconData(61977, fontFamily: 'qweather'), + "2051": const IconData(61978, fontFamily: 'qweather'), + "2052": const IconData(61895, fontFamily: 'qweather'), + "2053": const IconData(61896, fontFamily: 'qweather'), + "2054": const IconData(61897, fontFamily: 'qweather'), + "2070": const IconData(61979, fontFamily: 'qweather'), + "2071": const IconData(61980, fontFamily: 'qweather'), + "2072": const IconData(61981, fontFamily: 'qweather'), + "2073": const IconData(61982, fontFamily: 'qweather'), + "2074": const IconData(61983, fontFamily: 'qweather'), + "2075": const IconData(61984, fontFamily: 'qweather'), + "2076": const IconData(61985, fontFamily: 'qweather'), + "2077": const IconData(61986, fontFamily: 'qweather'), + "2078": const IconData(61987, fontFamily: 'qweather'), + "2079": const IconData(61988, fontFamily: 'qweather'), + "2080": const IconData(61989, fontFamily: 'qweather'), + "2081": const IconData(61990, fontFamily: 'qweather'), + "2082": const IconData(61991, fontFamily: 'qweather'), + "2083": const IconData(61992, fontFamily: 'qweather'), + "2084": const IconData(61993, fontFamily: 'qweather'), + "2085": const IconData(61994, fontFamily: 'qweather'), + "2100": const IconData(61995, fontFamily: 'qweather'), + "2101": const IconData(61996, fontFamily: 'qweather'), + "2102": const IconData(61997, fontFamily: 'qweather'), + "2103": const IconData(61998, fontFamily: 'qweather'), + "2104": const IconData(61999, fontFamily: 'qweather'), + "2105": const IconData(62000, fontFamily: 'qweather'), + "2106": const IconData(62001, fontFamily: 'qweather'), + "2107": const IconData(62002, fontFamily: 'qweather'), + "2108": const IconData(62003, fontFamily: 'qweather'), + "2109": const IconData(62004, fontFamily: 'qweather'), + "2111": const IconData(62005, fontFamily: 'qweather'), + "2120": const IconData(62006, fontFamily: 'qweather'), + "2121": const IconData(62007, fontFamily: 'qweather'), + "2122": const IconData(62008, fontFamily: 'qweather'), + "2123": const IconData(62009, fontFamily: 'qweather'), + "2124": const IconData(62010, fontFamily: 'qweather'), + "2125": const IconData(62011, fontFamily: 'qweather'), + "2126": const IconData(62012, fontFamily: 'qweather'), + "2127": const IconData(62013, fontFamily: 'qweather'), + "2128": const IconData(62014, fontFamily: 'qweather'), + "2129": const IconData(62015, fontFamily: 'qweather'), + "2130": const IconData(62016, fontFamily: 'qweather'), + "2131": const IconData(62017, fontFamily: 'qweather'), + "2132": const IconData(62018, fontFamily: 'qweather'), + "2133": const IconData(62019, fontFamily: 'qweather'), + "2134": const IconData(62020, fontFamily: 'qweather'), + "2135": const IconData(62021, fontFamily: 'qweather'), + "2150": const IconData(62022, fontFamily: 'qweather'), + "2151": const IconData(62023, fontFamily: 'qweather'), + "2152": const IconData(62024, fontFamily: 'qweather'), + "2153": const IconData(62025, fontFamily: 'qweather'), + "2154": const IconData(62026, fontFamily: 'qweather'), + "2155": const IconData(62027, fontFamily: 'qweather'), + "2156": const IconData(62028, fontFamily: 'qweather'), + "2157": const IconData(62029, fontFamily: 'qweather'), + "2158": const IconData(62030, fontFamily: 'qweather'), + "2159": const IconData(62031, fontFamily: 'qweather'), + "2160": const IconData(62032, fontFamily: 'qweather'), + "2161": const IconData(62033, fontFamily: 'qweather'), + "2162": const IconData(62034, fontFamily: 'qweather'), + "2163": const IconData(62035, fontFamily: 'qweather'), + "2164": const IconData(62036, fontFamily: 'qweather'), + "2165": const IconData(62037, fontFamily: 'qweather'), + "2166": const IconData(62038, fontFamily: 'qweather'), + "2190": const IconData(62039, fontFamily: 'qweather'), + "2191": const IconData(62040, fontFamily: 'qweather'), + "2192": const IconData(62041, fontFamily: 'qweather'), + "2193": const IconData(62042, fontFamily: 'qweather'), + "2200": const IconData(62180, fontFamily: 'qweather'), + "2201": const IconData(62181, fontFamily: 'qweather'), + "2202": const IconData(62182, fontFamily: 'qweather'), + "2203": const IconData(62183, fontFamily: 'qweather'), + "2204": const IconData(62184, fontFamily: 'qweather'), + "2205": const IconData(62185, fontFamily: 'qweather'), + "2207": const IconData(62186, fontFamily: 'qweather'), + "2208": const IconData(62187, fontFamily: 'qweather'), + "2209": const IconData(62188, fontFamily: 'qweather'), + "2210": const IconData(62189, fontFamily: 'qweather'), + "2211": const IconData(62190, fontFamily: 'qweather'), + "2212": const IconData(62191, fontFamily: 'qweather'), + "2213": const IconData(62192, fontFamily: 'qweather'), + "2214": const IconData(62193, fontFamily: 'qweather'), + "2215": const IconData(62194, fontFamily: 'qweather'), + "2216": const IconData(62195, fontFamily: 'qweather'), + "2217": const IconData(62196, fontFamily: 'qweather'), + "2218": const IconData(62197, fontFamily: 'qweather'), + "2300": const IconData(62043, fontFamily: 'qweather'), + "2301": const IconData(62044, fontFamily: 'qweather'), + "2302": const IconData(62045, fontFamily: 'qweather'), + "2303": const IconData(62046, fontFamily: 'qweather'), + "2304": const IconData(62047, fontFamily: 'qweather'), + "2305": const IconData(62048, fontFamily: 'qweather'), + "2306": const IconData(62049, fontFamily: 'qweather'), + "2307": const IconData(62050, fontFamily: 'qweather'), + "2308": const IconData(62051, fontFamily: 'qweather'), + "2309": const IconData(62052, fontFamily: 'qweather'), + "2311": const IconData(62053, fontFamily: 'qweather'), + "2312": const IconData(62054, fontFamily: 'qweather'), + "2313": const IconData(62055, fontFamily: 'qweather'), + "2314": const IconData(62056, fontFamily: 'qweather'), + "2315": const IconData(62057, fontFamily: 'qweather'), + "2316": const IconData(62058, fontFamily: 'qweather'), + "2317": const IconData(62059, fontFamily: 'qweather'), + "2318": const IconData(62060, fontFamily: 'qweather'), + "2319": const IconData(62061, fontFamily: 'qweather'), + "2320": const IconData(62062, fontFamily: 'qweather'), + "2321": const IconData(62063, fontFamily: 'qweather'), + "2322": const IconData(62064, fontFamily: 'qweather'), + "2323": const IconData(62065, fontFamily: 'qweather'), + "2324": const IconData(62066, fontFamily: 'qweather'), + "2325": const IconData(62067, fontFamily: 'qweather'), + "2326": const IconData(62068, fontFamily: 'qweather'), + "2327": const IconData(62069, fontFamily: 'qweather'), + "2328": const IconData(62070, fontFamily: 'qweather'), + "2330": const IconData(62071, fontFamily: 'qweather'), + "2331": const IconData(62072, fontFamily: 'qweather'), + "2332": const IconData(62073, fontFamily: 'qweather'), + "2333": const IconData(62074, fontFamily: 'qweather'), + "2341": const IconData(62075, fontFamily: 'qweather'), + "2343": const IconData(62076, fontFamily: 'qweather'), + "2345": const IconData(62077, fontFamily: 'qweather'), + "2346": const IconData(62078, fontFamily: 'qweather'), + "2348": const IconData(62079, fontFamily: 'qweather'), + "2349": const IconData(62080, fontFamily: 'qweather'), + "2350": const IconData(62081, fontFamily: 'qweather'), + "2351": const IconData(62082, fontFamily: 'qweather'), + "2352": const IconData(62083, fontFamily: 'qweather'), + "2353": const IconData(62084, fontFamily: 'qweather'), + "2354": const IconData(62085, fontFamily: 'qweather'), + "2355": const IconData(62086, fontFamily: 'qweather'), + "2356": const IconData(62087, fontFamily: 'qweather'), + "2357": const IconData(62088, fontFamily: 'qweather'), + "2358": const IconData(62089, fontFamily: 'qweather'), + "2359": const IconData(62090, fontFamily: 'qweather'), + "2360": const IconData(62091, fontFamily: 'qweather'), + "2361": const IconData(62092, fontFamily: 'qweather'), + "2362": const IconData(62093, fontFamily: 'qweather'), + "2363": const IconData(62094, fontFamily: 'qweather'), + "2364": const IconData(62095, fontFamily: 'qweather'), + "2365": const IconData(62096, fontFamily: 'qweather'), + "2366": const IconData(62097, fontFamily: 'qweather'), + "2367": const IconData(62098, fontFamily: 'qweather'), + "2368": const IconData(62099, fontFamily: 'qweather'), + "2369": const IconData(62100, fontFamily: 'qweather'), + "2370": const IconData(62101, fontFamily: 'qweather'), + "2371": const IconData(62102, fontFamily: 'qweather'), + "2372": const IconData(62103, fontFamily: 'qweather'), + "2373": const IconData(62104, fontFamily: 'qweather'), + "2374": const IconData(62105, fontFamily: 'qweather'), + "2375": const IconData(62106, fontFamily: 'qweather'), + "2376": const IconData(62107, fontFamily: 'qweather'), + "2377": const IconData(62108, fontFamily: 'qweather'), + "2378": const IconData(62109, fontFamily: 'qweather'), + "2379": const IconData(62110, fontFamily: 'qweather'), + "2380": const IconData(62111, fontFamily: 'qweather'), + "2381": const IconData(62112, fontFamily: 'qweather'), + "2382": const IconData(62113, fontFamily: 'qweather'), + "2383": const IconData(62114, fontFamily: 'qweather'), + "2384": const IconData(62115, fontFamily: 'qweather'), + "2385": const IconData(62116, fontFamily: 'qweather'), + "2386": const IconData(62117, fontFamily: 'qweather'), + "2387": const IconData(62118, fontFamily: 'qweather'), + "2388": const IconData(62119, fontFamily: 'qweather'), + "2389": const IconData(62120, fontFamily: 'qweather'), + "2390": const IconData(62121, fontFamily: 'qweather'), + "2391": const IconData(62122, fontFamily: 'qweather'), + "2392": const IconData(62123, fontFamily: 'qweather'), + "2393": const IconData(62124, fontFamily: 'qweather'), + "2394": const IconData(62125, fontFamily: 'qweather'), + "2395": const IconData(62126, fontFamily: 'qweather'), + "2396": const IconData(62127, fontFamily: 'qweather'), + "2397": const IconData(62128, fontFamily: 'qweather'), + "2398": const IconData(62129, fontFamily: 'qweather'), + "2399": const IconData(62130, fontFamily: 'qweather'), + "2400": const IconData(62131, fontFamily: 'qweather'), + "2409": const IconData(62132, fontFamily: 'qweather'), + "2411": const IconData(62133, fontFamily: 'qweather'), + "2412": const IconData(62134, fontFamily: 'qweather'), + "2413": const IconData(62135, fontFamily: 'qweather'), + "2414": const IconData(62136, fontFamily: 'qweather'), + "2415": const IconData(62137, fontFamily: 'qweather'), + "2416": const IconData(62138, fontFamily: 'qweather'), + "2417": const IconData(62139, fontFamily: 'qweather'), + "2418": const IconData(62140, fontFamily: 'qweather'), + "2419": const IconData(62141, fontFamily: 'qweather'), + "2420": const IconData(62142, fontFamily: 'qweather'), + "2421": const IconData(62143, fontFamily: 'qweather'), + "2422": const IconData(62144, fontFamily: 'qweather'), + "2423": const IconData(62145, fontFamily: 'qweather'), + "2424": const IconData(62146, fontFamily: 'qweather'), + "2425": const IconData(62147, fontFamily: 'qweather'), + "2426": const IconData(62148, fontFamily: 'qweather'), + "9998": const IconData(61898, fontFamily: 'qweather'), + "9999": const IconData(61899, fontFamily: 'qweather') + }; +} diff --git a/lib/common/values/keyboard_state.dart b/lib/common/values/keyboard_state.dart new file mode 100644 index 0000000..87f2ee5 --- /dev/null +++ b/lib/common/values/keyboard_state.dart @@ -0,0 +1 @@ +enum KeyboardState { unknown, opening, closing, closed } diff --git a/lib/common/values/size.dart b/lib/common/values/size.dart new file mode 100644 index 0000000..cec2e4b --- /dev/null +++ b/lib/common/values/size.dart @@ -0,0 +1 @@ +enum ScreenSize { watch, handset, tablet, desktop } diff --git a/lib/common/values/view_mode.dart b/lib/common/values/view_mode.dart new file mode 100644 index 0000000..4da2a3a --- /dev/null +++ b/lib/common/values/view_mode.dart @@ -0,0 +1 @@ +enum ViewMode { list, calendar } diff --git a/lib/components/audio_player/audio_player_logic.dart b/lib/components/audio_player/audio_player_logic.dart new file mode 100644 index 0000000..c0e72c1 --- /dev/null +++ b/lib/components/audio_player/audio_player_logic.dart @@ -0,0 +1,80 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/animation.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/edit/edit_logic.dart'; + +import 'audio_player_state.dart'; + +class AudioPlayerLogic extends GetxController with GetSingleTickerProviderStateMixin { + final AudioPlayerState state = AudioPlayerState(); + + final AudioPlayer audioPlayer = AudioPlayer(); + late final AnimationController animationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 100)); + late final EditLogic editLogic = Bind.find(); + + @override + void onInit() { + // TODO: implement onInit + audioPlayer.eventStream.listen((event) { + //读取时间完成 + if (event.eventType == AudioEventType.duration) { + state.totalDuration.value = event.duration!; + } + //播放完成 + if (event.eventType == AudioEventType.complete) { + animationController.reset(); + state.currentDuration.value = Duration.zero; + audioPlayer.stop(); + } + }); + + audioPlayer.onPositionChanged.listen((duration) { + if (state.handleChange.value == false) { + state.currentDuration.value = duration; + } + }); + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + animationController.dispose(); + audioPlayer.dispose(); + super.onClose(); + } + + //初始化获取时长信息 + Future initAudioPlayer(String value) async { + await audioPlayer.setSourceDeviceFile(value); + } + + Future play(String path) async { + await animationController.forward(); + await audioPlayer.play(DeviceFileSource(path)); + } + + Future pause() async { + await animationController.reverse(); + await audioPlayer.pause(); + } + + Future to(value) async { + await audioPlayer.seek(state.currentDuration.value); + state.handleChange.value = false; + } + + Future changeValue(value) async { + state.handleChange.value = true; + state.currentDuration.value = Duration(milliseconds: (state.totalDuration.value.inMilliseconds * value).toInt()); + } +} diff --git a/lib/components/audio_player/audio_player_state.dart b/lib/components/audio_player/audio_player_state.dart new file mode 100644 index 0000000..fd48a55 --- /dev/null +++ b/lib/components/audio_player/audio_player_state.dart @@ -0,0 +1,17 @@ +import 'package:get/get.dart'; + +class AudioPlayerState { + late String audioPath; + late Rx totalDuration; + late Rx currentDuration; + late RxBool handleChange; + + AudioPlayerState() { + audioPath = ''; + handleChange = false.obs; + totalDuration = Duration.zero.obs; + currentDuration = Duration.zero.obs; + + ///Initialize variables + } +} diff --git a/lib/components/audio_player/audio_player_view.dart b/lib/components/audio_player/audio_player_view.dart new file mode 100644 index 0000000..fcfe439 --- /dev/null +++ b/lib/components/audio_player/audio_player_view.dart @@ -0,0 +1,100 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'audio_player_logic.dart'; + +class AudioPlayerComponent extends StatelessWidget { + const AudioPlayerComponent({super.key, required this.path, required this.index, this.isEdit = false}); + + final String path; + final String index; + + final bool isEdit; + + @override + Widget build(BuildContext context) { + final logic = Get.put(AudioPlayerLogic(), tag: index); + final state = Bind.find(tag: index).state; + + final colorScheme = Theme.of(context).colorScheme; + return GetBuilder( + init: logic, + tag: index, + assignId: true, + initState: (_) async { + await logic.initAudioPlayer(path); + }, + builder: (logic) { + return Container( + decoration: BoxDecoration(color: colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(8.0)), + constraints: const BoxConstraints(maxWidth: 400), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + IconButton.filled( + onPressed: () { + logic.audioPlayer.state == PlayerState.playing ? logic.pause() : logic.play(path); + }, + icon: AnimatedIcon( + icon: AnimatedIcons.play_pause, + progress: logic.animationController, + color: colorScheme.onPrimary, + )), + Expanded( + child: Column( + children: [ + Obx(() { + return Slider( + value: state.totalDuration.value != Duration.zero + ? ((state.currentDuration.value.inMilliseconds / state.totalDuration.value.inMilliseconds) + .clamp(0, 1)) + : 0, + onChangeEnd: (value) { + logic.to(value); + }, + onChanged: (double value) { + logic.changeValue(value); + }, + inactiveColor: colorScheme.outline, + ); + }), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Obx(() { + return Text( + state.totalDuration.value.toString().split('.')[0].padLeft(8, '0'), + style: TextStyle(color: colorScheme.onSecondaryContainer), + ); + }), + Obx(() { + return Text( + state.currentDuration.value.toString().split('.')[0].padLeft(8, '0'), + style: TextStyle(color: colorScheme.onSecondaryContainer), + ); + }), + ], + ), + ), + ], + ), + ), + if (isEdit) ...[ + IconButton( + onPressed: () { + logic.editLogic.deleteAudio(int.parse(index)); + }, + icon: const Icon(Icons.cancel), + style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + ) + ] + ], + ), + ); + }, + ); + } +} diff --git a/lib/components/category_add/category_add_logic.dart b/lib/components/category_add/category_add_logic.dart new file mode 100644 index 0000000..734bc69 --- /dev/null +++ b/lib/components/category_add/category_add_logic.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; +import 'package:mood_diary/pages/edit/edit_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'category_add_state.dart'; + +class CategoryAddLogic extends GetxController { + final CategoryAddState state = CategoryAddState(); + late TextEditingController textEditingController = TextEditingController(); + late EditLogic editLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + getCategory(); + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + textEditingController.dispose(); + super.onClose(); + } + + Future getCategory() async { + state.categoryList.value = Utils().isarUtil.getAllCategory(); + } + + Future addCategory() async { + if (textEditingController.text.isNotEmpty) { + if (await Utils().isarUtil.insertACategory(Category()..categoryName = textEditingController.text)) { + Get.backLegacy(); + await getCategory(); + } else { + textEditingController.clear(); + Utils().noticeUtil.showToast('分类已存在'); + } + } + } + + void cancelAdd() { + textEditingController.clear(); + Get.backLegacy(); + } + + void selectCategory(int index) { + Get.backLegacy(); + editLogic.selectCategory(state.categoryList.value[index].id); + } +} diff --git a/lib/components/category_add/category_add_state.dart b/lib/components/category_add/category_add_state.dart new file mode 100644 index 0000000..4068e26 --- /dev/null +++ b/lib/components/category_add/category_add_state.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; + +class CategoryAddState { + late RxList categoryList; + + CategoryAddState() { + categoryList = [].obs; + + ///Initialize variables + } +} diff --git a/lib/components/category_add/category_add_view.dart b/lib/components/category_add/category_add_view.dart new file mode 100644 index 0000000..985daa4 --- /dev/null +++ b/lib/components/category_add/category_add_view.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; + +import 'category_add_logic.dart'; + +class CategoryAddComponent extends StatelessWidget { + const CategoryAddComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(CategoryAddLogic()); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final i18n = AppLocalizations.of(context)!; + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(8.0, .0, 8.0, 8.0), + child: Obx(() { + return Wrap( + spacing: 8.0, + children: [ + FilledButton.icon( + icon: const Icon(Icons.add), + label: const Text('添加'), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextField( + maxLines: 1, + controller: logic.textEditingController, + decoration: InputDecoration( + fillColor: colorScheme.secondaryContainer, + border: const UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide.none, + ), + filled: true, + labelText: '标签', + ), + ), + actions: [ + TextButton( + onPressed: () { + logic.cancelAdd(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () async { + await logic.addCategory(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + ), + ...List.generate(state.categoryList.value.length, (index) { + return ActionChip( + label: Text(state.categoryList.value[index].categoryName), + onPressed: () { + logic.selectCategory(index); + }, + backgroundColor: colorScheme.secondaryContainer, + ); + }), + ], + ); + }), + ), + ], + ); + }, + ); + } +} diff --git a/lib/components/color_dialog/color_dialog_logic.dart b/lib/components/color_dialog/color_dialog_logic.dart new file mode 100644 index 0000000..199f7cd --- /dev/null +++ b/lib/components/color_dialog/color_dialog_logic.dart @@ -0,0 +1,44 @@ +import 'dart:ui'; + +import 'package:get/get.dart'; +import 'package:mood_diary/pages/setting/setting_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'color_dialog_state.dart'; + +class ColorDialogLogic extends GetxController { + final ColorDialogState state = ColorDialogState(); + + late SettingLogic settingLogic = Bind.find(); + + @override + void onInit() async { + // TODO: implement onInit + //如果支持系统颜色,获取系统颜色 + if (state.supportDynamic.value) { + state.systemColor.value = await Utils().themeUtil.getDynamicColor(); + } + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + //更改主题色 + Future changeSeedColor(index) async { + await Utils().prefUtil.setValue('color', index); + state.currentColor.value = index; + settingLogic.state.color.value = index; + Get.changeTheme(await Utils().themeUtil.buildTheme(Brightness.light)); + Get.changeTheme(await Utils().themeUtil.buildTheme(Brightness.dark)); + } +} diff --git a/lib/components/color_dialog/color_dialog_state.dart b/lib/components/color_dialog/color_dialog_state.dart new file mode 100644 index 0000000..b0030fb --- /dev/null +++ b/lib/components/color_dialog/color_dialog_state.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class ColorDialogState { + late RxBool supportDynamic; + late RxInt currentColor; + + late Rx systemColor; + + ColorDialogState() { + supportDynamic = Utils().prefUtil.getValue('supportDynamicColor')!.obs; + currentColor = Utils().prefUtil.getValue('color')!.obs; + systemColor = Colors.transparent.obs; + + ///Initialize variables + } +} diff --git a/lib/components/color_dialog/color_dialog_view.dart b/lib/components/color_dialog/color_dialog_view.dart new file mode 100644 index 0000000..ff27acc --- /dev/null +++ b/lib/components/color_dialog/color_dialog_view.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/colors.dart'; + +import 'color_dialog_logic.dart'; + +class ColorDialogComponent extends StatelessWidget { + const ColorDialogComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(ColorDialogLogic()); + final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + String colorName(index) { + return switch (index) { + 0 => i18n.colorNameBaiCaoShuang, + 1 => i18n.colorNameZhuYue, + 2 => i18n.colorNameLvLiuLi, + 3 => i18n.colorNameJin, + 4 => i18n.colorNameShiYangJin, + _ => i18n.colorNameSystem + }; + } + + List buildColorList() { + return List.generate(AppColor.colorList.length, (index) { + return SimpleDialogOption( + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10.0, + children: [ + Icon( + Icons.circle, + color: AppColor.colorList[index], + ), + Text(colorName(index)), + if (state.currentColor.value == index) ...[const Icon(Icons.check)] + ], + ), + onPressed: () { + logic.changeSeedColor(index); + }, + ); + }); + } + + Widget buildSystemColor() { + return SimpleDialogOption( + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 10.0, + children: [ + Obx(() { + return Icon( + Icons.circle, + color: state.systemColor.value, + ); + }), + Text(i18n.colorNameSystem), + if (state.currentColor.value == -1) ...[const Icon(Icons.check)] + ], + ), + onPressed: () { + logic.changeSeedColor(-1); + }, + ); + } + + return GetBuilder( + assignId: true, + init: logic, + builder: (logic) { + return Obx(() { + return SimpleDialog( + title: Text(i18n.settingColor), + children: [ + if (state.supportDynamic.value) ...[buildSystemColor()], + ...buildColorList() + ], + ); + }); + }, + ); + } +} diff --git a/lib/components/dashboard/dashboard_logic.dart b/lib/components/dashboard/dashboard_logic.dart new file mode 100644 index 0000000..52bb5e3 --- /dev/null +++ b/lib/components/dashboard/dashboard_logic.dart @@ -0,0 +1,90 @@ +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'dashboard_state.dart'; + +class DashboardLogic extends GetxController { + final DashboardState state = DashboardState(); + + @override + void onInit() { + // TODO: implement onInit + getUseTime(); + getDiaryCount(); + getMoodAndWeatherByRange(state.dateRange[0], state.dateRange[1]); + getCountContent(); + getCategoryCount(); + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + Future getCountContent() async { + var list = await Utils().isarUtil.getContentList(); + int count = await compute(Utils().arrayUtil.countListItemLength, list); + state.contentCount.value = count.toString(); + } + + Future getDiaryCount() async { + int count = await Utils().isarUtil.countDiaries(); + state.diaryCount.value = count.toString(); + } + + Future getCategoryCount() async { + int count = await Utils().isarUtil.countCategories(); + state.categoryCount.value = count.toString(); + } + + //选中两个日期后,查询指定范围内的日记 + Future getMoodAndWeatherByRange(DateTime start, DateTime end) async { + var moodList = await Utils().isarUtil.getMoodByDateRange(start, end.subtract(const Duration(days: -1))); + var weatherList = await Utils().isarUtil.getWeatherByDateRange(start, end.subtract(const Duration(days: -1))); + + //去掉没有天气 + weatherList.removeWhere((item) => item.isEmpty); + if (moodList.isNotEmpty) { + state.recentMood.value = + Utils().arrayUtil.countList(moodList).entries.reduce((a, b) => a.value > b.value ? a : b).key.toString(); + } else { + state.recentMood.value = 'none'; + } + if (weatherList.isNotEmpty) { + var weatherCode = List.generate(weatherList.length, (index) => weatherList[index].first); + state.recentWeather.value = + Utils().arrayUtil.countList(weatherCode).entries.reduce((a, b) => a.value > b.value ? a : b).key; + } else { + state.recentWeather.value = 'none'; + } + } + + Future getUseTime() async { + DateTime firstStart = DateTime.fromMillisecondsSinceEpoch(Utils().prefUtil.getValue('startTime')!); + Duration duration = DateTime.now().difference(firstStart); + state.useTime.value = (duration.inDays + 1).toString(); + } + + Future toAnalysePage() async { + Get.toNamed(AppRoutes.analysePage); + } + + Future toDiaryManager() async { + Get.toNamed(AppRoutes.diaryManagerPage); + } + + Future toCategoryManager() async { + await Get.toNamed(AppRoutes.categoryManagerPage); + await getCategoryCount(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/components/dashboard/dashboard_state.dart b/lib/components/dashboard/dashboard_state.dart new file mode 100644 index 0000000..90136bd --- /dev/null +++ b/lib/components/dashboard/dashboard_state.dart @@ -0,0 +1,33 @@ +import 'package:get/get.dart'; + +class DashboardState { + //日记数量 + late RxString diaryCount; + + //日记文字总数 + late RxString contentCount; + + //使用时间 + late RxString useTime; + + //分类数量 + late RxString categoryCount; + + //分析近期的日期默认一周 + late List dateRange; + late RxString recentMood; + late RxString recentWeather; + + DashboardState() { + var now = DateTime.now().copyWith(hour: 0, minute: 0, second: 0); + dateRange = [now.subtract(const Duration(days: 6)), now]; + diaryCount = ''.obs; + categoryCount = ''.obs; + contentCount = ''.obs; + recentMood = ''.obs; + recentWeather = ''.obs; + useTime = ''.obs; + + ///Initialize variables + } +} diff --git a/lib/components/dashboard/dashboard_view.dart b/lib/components/dashboard/dashboard_view.dart new file mode 100644 index 0000000..3321286 --- /dev/null +++ b/lib/components/dashboard/dashboard_view.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/icons.dart'; +import 'package:mood_diary/components/mood_icon/mood_icon_view.dart'; + +import 'dashboard_logic.dart'; + +class DashboardComponent extends StatelessWidget { + const DashboardComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(DashboardLogic()); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + + Widget buildManagerButton(IconData icon, String label, Widget content, Function()? onTap) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(12.0), + child: Card.outlined( + color: colorScheme.surfaceContainerLow, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + icon, + color: colorScheme.secondary, + size: 32, + ), + Text( + label, + style: textStyle.labelLarge, + ) + ], + ), + const SizedBox( + width: 8.0, + ), + Expanded(child: Center(child: content)) + ], + ), + ), + ), + ); + } + + Widget buildDetail(String content, String unit) { + return content == '' + ? const CircularProgressIndicator() + : RichText( + text: TextSpan(children: [ + TextSpan(text: '$content ', style: textStyle.displaySmall!.copyWith(color: colorScheme.tertiary)), + TextSpan(text: unit, style: textStyle.labelLarge) + ]), + ); + } + + Widget buildDiaryDetail(String content1, String unit1, String content2, String unit2) { + return content1 == '' || content2 == '' + ? const CircularProgressIndicator() + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan(children: [ + TextSpan(text: '$content1 ', style: textStyle.titleLarge!.copyWith(color: colorScheme.tertiary)), + TextSpan(text: unit1, style: textStyle.labelLarge) + ]), + ), + const SizedBox( + height: 8.0, + ), + RichText( + text: TextSpan(children: [ + TextSpan(text: '$content2 ', style: textStyle.titleLarge!.copyWith(color: colorScheme.tertiary)), + TextSpan(text: unit2, style: textStyle.labelLarge) + ]), + ), + ], + ), + ); + } + + Widget buildAnalyse(String mood, String weather) { + return mood == '' || weather == '' + ? const CircularProgressIndicator() + : Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (weather != 'none') ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + '天气', + style: textStyle.labelLarge, + ), + Icon( + WeatherIcon.map[weather], + color: colorScheme.tertiary, + ), + ], + ), + ], + if (weather != 'none' && mood != 'none') ...[ + const SizedBox( + height: 8.0, + ) + ], + if (mood != 'none') ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + '心情', + style: textStyle.labelLarge, + ), + MoodIconComponent( + value: double.parse(mood), + width: 24.0, + ) + ], + ) + ], + if (weather == 'none' && mood == 'none') ...[const Text('暂无')] + ], + ); + } + + return GetBuilder( + init: logic, + assignId: true, + autoRemove: false, + builder: (logic) { + return GridView( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisSpacing: 8.0, + crossAxisSpacing: 8.0, + childAspectRatio: 1.618, + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: [ + buildManagerButton(Icons.calendar_month, '使用', Obx(() { + return buildDetail(state.useTime.value, '天'); + }), null), + buildManagerButton(Icons.article, '日记', Obx(() { + return buildDiaryDetail(state.diaryCount.value, '篇', state.contentCount.value, '字'); + }), () { + logic.toDiaryManager(); + }), + buildManagerButton(Icons.category, '分类', Obx(() { + return buildDetail(state.categoryCount.value, '个'); + }), () { + logic.toCategoryManager(); + }), + buildManagerButton(Icons.analytics, '分析', Obx(() { + return buildAnalyse(state.recentMood.value, state.recentWeather.value); + }), () { + logic.toAnalysePage(); + }), + ], + ); + }, + ); + } +} diff --git a/lib/components/diary_card/large_diary_card/large_diary_card_logic.dart b/lib/components/diary_card/large_diary_card/large_diary_card_logic.dart new file mode 100644 index 0000000..83c57e4 --- /dev/null +++ b/lib/components/diary_card/large_diary_card/large_diary_card_logic.dart @@ -0,0 +1,31 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; + +import 'large_diary_card_state.dart'; + +class LargeDiaryCardLogic extends GetxController { + final LargeDiaryCardState state = LargeDiaryCardState(); + late HomeLogic homeLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future toDiary(Diary diary) async { + var res = await Get.toNamed(AppRoutes.diaryPage, arguments: diary); + //返回后切换回app主题 + if (res == 'delete') { + await homeLogic.updateDiary(); + } + } +} diff --git a/lib/components/diary_card/large_diary_card/large_diary_card_state.dart b/lib/components/diary_card/large_diary_card/large_diary_card_state.dart new file mode 100644 index 0000000..49b97d1 --- /dev/null +++ b/lib/components/diary_card/large_diary_card/large_diary_card_state.dart @@ -0,0 +1,5 @@ +class LargeDiaryCardState { + LargeDiaryCardState() { + ///Initialize variables + } +} diff --git a/lib/components/diary_card/large_diary_card/large_diary_card_view.dart b/lib/components/diary_card/large_diary_card/large_diary_card_view.dart new file mode 100644 index 0000000..94d3437 --- /dev/null +++ b/lib/components/diary_card/large_diary_card/large_diary_card_view.dart @@ -0,0 +1,98 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'large_diary_card_logic.dart'; + +class LargeDiaryCardComponent extends StatelessWidget { + const LargeDiaryCardComponent({super.key, required this.diary, required this.tag}); + + final Diary diary; + + final String tag; + + @override + Widget build(BuildContext context) { + final logic = Get.put(LargeDiaryCardLogic(), tag: tag); + + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + + Widget buildImage() { + return Container( + height: 154.0, + decoration: BoxDecoration( + image: DecorationImage( + image: FileImage(File(Utils().fileUtil.getRealPath('image', diary.imageName.first))), + fit: BoxFit.cover, + ), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + ); + } + + return GetBuilder( + init: logic, + assignId: true, + tag: tag, + autoRemove: false, + builder: (logic) { + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + onTap: () async { + await logic.toDiary(diary); + }, + child: Card.filled( + color: colorScheme.surfaceContainer, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 252), + child: Column( + children: [ + if (diary.imageName.isNotEmpty) ...[buildImage()], + Expanded( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (diary.title != null) ...[ + Text( + diary.title!, + overflow: TextOverflow.ellipsis, + style: textStyle.titleMedium!.copyWith(), + maxLines: 1, + ) + ], + Text( + diary.contentText.trim(), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: textStyle.bodyMedium, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat.yMMMd().add_Hms().format(diary.time), + style: textStyle.labelSmall, + ), + ], + ) + ], + ), + ), + ) + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/diary_card/small_diary_card/small_diary_card_logic.dart b/lib/components/diary_card/small_diary_card/small_diary_card_logic.dart new file mode 100644 index 0000000..14f65e5 --- /dev/null +++ b/lib/components/diary_card/small_diary_card/small_diary_card_logic.dart @@ -0,0 +1,27 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; + +import 'small_diary_card_state.dart'; + +class SmallDiaryCardLogic extends GetxController { + final SmallDiaryCardState state = SmallDiaryCardState(); + late HomeLogic homeLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future toDiary(Diary diary) async { + await Get.toNamed(AppRoutes.diaryPage, arguments: diary); + } +} diff --git a/lib/components/diary_card/small_diary_card/small_diary_card_state.dart b/lib/components/diary_card/small_diary_card/small_diary_card_state.dart new file mode 100644 index 0000000..96ef497 --- /dev/null +++ b/lib/components/diary_card/small_diary_card/small_diary_card_state.dart @@ -0,0 +1,5 @@ +class SmallDiaryCardState { + SmallDiaryCardState() { + ///Initialize variables + } +} diff --git a/lib/components/diary_card/small_diary_card/small_diary_card_view.dart b/lib/components/diary_card/small_diary_card/small_diary_card_view.dart new file mode 100644 index 0000000..6c37a17 --- /dev/null +++ b/lib/components/diary_card/small_diary_card/small_diary_card_view.dart @@ -0,0 +1,99 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'small_diary_card_logic.dart'; + +class SmallDiaryCardComponent extends StatelessWidget { + const SmallDiaryCardComponent({super.key, required this.diary, required this.tag}); + + final Diary diary; + + final String tag; + + @override + Widget build(BuildContext context) { + final logic = Get.put(SmallDiaryCardLogic(), tag: tag); + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + Widget buildImage() { + return AspectRatio( + aspectRatio: 1.0, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: FileImage(File(Utils().fileUtil.getRealPath('image', diary.imageName.first))), + fit: BoxFit.cover, + ), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + ), + ); + } + + return GetBuilder( + init: logic, + assignId: true, + tag: tag, + autoRemove: false, + builder: (logic) { + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + onTap: () async { + await logic.toDiary(diary); + }, + child: Card.filled( + color: colorScheme.surfaceContainer, + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 122), + child: Row( + children: [ + if (diary.imageName.isNotEmpty && int.parse(tag) & 1 == 0) ...[buildImage()], + Expanded( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (diary.title != null) ...[ + Text( + diary.title!, + overflow: TextOverflow.ellipsis, + style: textStyle.titleMedium, + maxLines: 1, + ) + ], + Text( + diary.contentText.trim(), + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: textStyle.bodyMedium, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat.yMMMd().add_Hms().format(diary.time), + style: textStyle.labelSmall, + ), + ], + ) + ], + ), + ), + ), + if (diary.imageName.isNotEmpty && int.parse(tag) & 1 == 1) ...[buildImage()], + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/home_tab_view/home_tab_view_logic.dart b/lib/components/home_tab_view/home_tab_view_logic.dart new file mode 100644 index 0000000..5d13f2c --- /dev/null +++ b/lib/components/home_tab_view/home_tab_view_logic.dart @@ -0,0 +1,53 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'home_tab_view_state.dart'; + +class HomeTabViewLogic extends GetxController { + final HomeTabViewState state = HomeTabViewState(); + + @override + void onInit() { + // TODO: implement onInit + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + + super.onClose(); + } + + Future initDiary(String? categoryId) async { + state.categoryId = categoryId; + await getDiary(); + } + + Future getDiary() async { + state.isFetching.value = true; + //如果分类不为空说明是按照分类查询,否则查询全部 + if (state.categoryId != null) { + state.diaryList.value = await Utils().isarUtil.getDiaryByCategory(state.categoryId!, 0, 10); + } else { + //默认分页10篇 + state.diaryList.value = await Utils().isarUtil.getAllDiary(0, 10); + } + state.isFetching.value = false; + } + + Future paginationDiary(int offset, int limit) async { + if (state.categoryId != null) { + state.diaryList.value = await Utils().isarUtil.getDiaryByCategory(state.categoryId!, offset, limit); + } else { + state.diaryList.value += await Utils().isarUtil.getAllDiary(offset, limit); + } + } +} diff --git a/lib/components/home_tab_view/home_tab_view_state.dart b/lib/components/home_tab_view/home_tab_view_state.dart new file mode 100644 index 0000000..f36de7f --- /dev/null +++ b/lib/components/home_tab_view/home_tab_view_state.dart @@ -0,0 +1,17 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; + +class HomeTabViewState { + late RxList diaryList; + + late RxBool isFetching; + + late String? categoryId; + + HomeTabViewState() { + diaryList = [].obs; + isFetching = false.obs; + + ///Initialize variables + } +} diff --git a/lib/components/home_tab_view/home_tab_view_view.dart b/lib/components/home_tab_view/home_tab_view_view.dart new file mode 100644 index 0000000..793916b --- /dev/null +++ b/lib/components/home_tab_view/home_tab_view_view.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/diary_card/large_diary_card/large_diary_card_view.dart'; +import 'package:mood_diary/components/diary_card/small_diary_card/small_diary_card_view.dart'; + +import 'home_tab_view_logic.dart'; + +class HomeTabViewComponent extends StatelessWidget { + const HomeTabViewComponent({super.key, this.categoryId, required this.tag}); + + final String? categoryId; + final String tag; + + @override + Widget build(BuildContext context) { + final logic = Get.put(HomeTabViewLogic(), tag: tag); + final state = Bind.find(tag: tag).state; + + Widget buildGrid() { + return MasonryGridView.builder( + gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 440), + padding: const EdgeInsets.symmetric(horizontal: 4.0), + shrinkWrap: true, + itemBuilder: (context, index) { + final aspect = state.diaryList.value[index].aspect; + //如果是横图,显示大卡片,竖图或者没有图显示小卡片 + if (aspect != null && aspect > 1.0) { + return LargeDiaryCardComponent(tag: index.toString(), diary: state.diaryList.value[index]); + } else { + return SmallDiaryCardComponent(tag: index.toString(), diary: state.diaryList.value[index]); + } + }, + itemCount: state.diaryList.value.length, + ); + } + + return GetBuilder( + assignId: true, + init: logic, + tag: tag, + initState: (_) { + logic.initDiary(categoryId); + }, + builder: (logic) { + return Obx(() { + return state.isFetching.value + ? const Center(child: CircularProgressIndicator()) + : (state.diaryList.value.isNotEmpty + ? buildGrid() + : const Center( + child: Text('这里一片荒芜'), + )); + }); + }, + ); + } +} diff --git a/lib/components/login_form/login_form_logic.dart b/lib/components/login_form/login_form_logic.dart new file mode 100644 index 0000000..7d346f2 --- /dev/null +++ b/lib/components/login_form/login_form_logic.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/login/login_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'login_form_state.dart'; + +class LoginFormLogic extends GetxController { + final LoginFormState state = LoginFormState(); + + late final loginLogic = Bind.find(); + + final FocusNode emailFocusNode = FocusNode(); + final FocusNode passwordFocusNode = FocusNode(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + emailFocusNode.dispose(); + passwordFocusNode.dispose(); + + super.onClose(); + } + + Future submit() async { + if (state.formKey.currentState!.validate()) { + state.formKey.currentState!.save(); + await Utils().supabaseUtil.signIn(state.email, state.password); + Get.offAndToNamed(AppRoutes.userPage); + } + } +} diff --git a/lib/components/login_form/login_form_state.dart b/lib/components/login_form/login_form_state.dart new file mode 100644 index 0000000..450ea6a --- /dev/null +++ b/lib/components/login_form/login_form_state.dart @@ -0,0 +1,15 @@ +import 'package:flutter/material.dart'; + +class LoginFormState { + final formKey = GlobalKey(); + + late String email; + late String password; + + LoginFormState() { + email = ''; + password = ''; + + ///Initialize variables + } +} diff --git a/lib/components/login_form/login_form_view.dart b/lib/components/login_form/login_form_view.dart new file mode 100644 index 0000000..bcb1a04 --- /dev/null +++ b/lib/components/login_form/login_form_view.dart @@ -0,0 +1,105 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/function_extensions.dart'; + +import 'login_form_logic.dart'; + +class LoginFormComponent extends StatelessWidget { + const LoginFormComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(LoginFormLogic()); + final state = Bind.find().state; + final size = MediaQuery.sizeOf(context); + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return GestureDetector( + onTap: () { + logic.emailFocusNode.unfocus(); + logic.passwordFocusNode.unfocus(); + }, + behavior: HitTestBehavior.opaque, + child: Center( + child: SizedBox( + width: min(300, size.width / 1.618), + child: Form( + key: state.formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: '邮箱', + ), + onSaved: (value) { + state.email = value!; + }, + onChanged: (value) { + state.email = value; + }, + validator: (value) { + return (value == null || !GetUtils.isEmail(value)) ? '请输入邮箱' : null; + }, + focusNode: logic.emailFocusNode, + ), + const SizedBox( + height: 20.0, + ), + TextFormField( + validator: (value) { + if (value!.isEmpty) { + return '请输入密码'; + } else if (value.length < 6) { + return '密码最少六位'; + } else { + return null; + } + }, + onSaved: (value) { + state.password = value!; + }, + onChanged: (value) { + state.password = value; + }, + obscureText: true, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: '密码', + ), + focusNode: logic.passwordFocusNode, + ), + const SizedBox( + height: 20.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + logic.loginLogic.changeForm(); + }, + child: const Text("注册")), + ElevatedButton( + onPressed: () { + logic.submit(); + }.throttleWithTimeout(timeout: 5000), + child: const Icon(Icons.login), + ), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/mood_icon/mood_icon_logic.dart b/lib/components/mood_icon/mood_icon_logic.dart new file mode 100644 index 0000000..6f42eb5 --- /dev/null +++ b/lib/components/mood_icon/mood_icon_logic.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; + +import 'mood_icon_state.dart'; + +class MoodIconLogic extends GetxController { + final MoodIconState state = MoodIconState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/components/mood_icon/mood_icon_state.dart b/lib/components/mood_icon/mood_icon_state.dart new file mode 100644 index 0000000..9ee0835 --- /dev/null +++ b/lib/components/mood_icon/mood_icon_state.dart @@ -0,0 +1,5 @@ +class MoodIconState { + MoodIconState() { + ///Initialize variables + } +} diff --git a/lib/components/mood_icon/mood_icon_view.dart b/lib/components/mood_icon/mood_icon_view.dart new file mode 100644 index 0000000..fc76b9f --- /dev/null +++ b/lib/components/mood_icon/mood_icon_view.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +class EmotionCurvePainter extends CustomPainter { + final double value; + final double strokeWidth; + + EmotionCurvePainter(this.value, {required this.strokeWidth}); + + @override + void paint(Canvas canvas, Size size) { + Paint paint = Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeCap = StrokeCap.round + ..strokeWidth = strokeWidth; + + Path path = Path(); + + double centerX = size.width / 2; + double centerY = size.height / 2; + double controlPointY = centerY + (value - 0.5) * size.height; // 动态控制点的Y坐标 + + // 起点 + path.moveTo(centerX + strokeWidth / 2 - size.width / 2, centerY); + + // 控制点和终点 + path.quadraticBezierTo(centerX, controlPointY, centerX - strokeWidth / 2 + size.width / 2, centerY); + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +class MoodIconComponent extends StatelessWidget { + const MoodIconComponent({super.key, this.width = 32.0, required this.value}); + + final double value; + + final double width; + + @override + Widget build(BuildContext context) { + // final logic = Get.put(MoodIconLogic()); + // final state = Bind.find().state; + + return Container( + decoration: BoxDecoration( + color: Color.lerp(const Color(0xFFFA4659), const Color(0xFF2EB872), value), + borderRadius: const BorderRadius.all(Radius.circular(8.0))), + padding: const EdgeInsets.all(4.0), + child: CustomPaint( + size: Size(width - 8.0, width - 8.0), + painter: EmotionCurvePainter(value, strokeWidth: 4.0), + ), + ); + } +} diff --git a/lib/components/record_sheet/record_sheet_logic.dart b/lib/components/record_sheet/record_sheet_logic.dart new file mode 100644 index 0000000..4e02f3e --- /dev/null +++ b/lib/components/record_sheet/record_sheet_logic.dart @@ -0,0 +1,114 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/edit/edit_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:record/record.dart'; +import 'package:uuid/uuid.dart'; + +import 'record_sheet_state.dart'; + +class RecordSheetLogic extends GetxController with GetSingleTickerProviderStateMixin { + final RecordSheetState state = RecordSheetState(); + final AudioRecorder audioRecorder = AudioRecorder(); + late AnimationController animationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 100), lowerBound: 0, upperBound: 1.0); + final size = MediaQuery.sizeOf(Get.context!); + + late final editLogic = Bind.find(); + + late double baseline = .0; + + @override + void onReady() { + // TODO: implement onReady + listenAmplitude(); + super.onReady(); + } + + @override + void onClose() async { + // TODO: implement onClose + if (state.isStop == false) { + await audioRecorder.cancel(); + } + audioRecorder.dispose(); + animationController.dispose(); + super.onClose(); + } + + Future startRecorder() async { + if (await Utils().permissionUtil.checkPermission(Permission.microphone)) { + await animationController.forward(); + state.isRecording.value = true; + state.isStarted.value = true; + state.height.value = 140.0; + state.fileName = 'audio-${const Uuid().v7()}.m4a'; + //暂时保存在缓存目录中 + await audioRecorder.start(const RecordConfig(encoder: AudioEncoder.aacLc), + path: Utils().fileUtil.getCachePath(state.fileName)); + } + } + + void listenAmplitude() { + final amplitudeStream = audioRecorder.onAmplitudeChanged(const Duration(milliseconds: 100)); + amplitudeStream.listen((amplitude) { + Utils().logUtil.printInfo(amplitude.current); + if (amplitude.current.isInfinite) { + maxLengthAdd(.0); + } else if (amplitude.current != amplitude.max) { + maxLengthAdd(normalizeAmplitude(amplitude.current)); + } + timeIncrease(); + }); + } + + double normalizeAmplitude(double amplitude) { + baseline = min(baseline, amplitude); + return (amplitude + baseline.abs()) / baseline.abs(); + } + + void maxLengthAdd(value) { + if (state.amplitudes.length > size.width ~/ 6.0) { + state.amplitudes.removeAt(0); + } + state.amplitudes.add(value); + } + + void timeIncrease() { + state.durationTime.value += const Duration(milliseconds: 100); + } + + Future stopRecorder() async { + state.isStop = true; + await audioRecorder.stop(); + animationController.reset(); + editLogic.setAudioName(state.fileName); + Get.backLegacy(); + } + + Future pauseRecorder() async { + state.isRecording.value = false; + await audioRecorder.pause(); + await animationController.reverse(); + } + + Future resumeRecorder() async { + await animationController.forward(); + state.isRecording.value = true; + await audioRecorder.resume(); + } + + Future cancelRecorder() async { + state.amplitudes.value = []; + state.durationTime.value = const Duration(); + animationController.reset(); + state.isStarted.value = false; + state.isRecording.value = false; + state.height.value = 0; + await audioRecorder.cancel(); + } +} diff --git a/lib/components/record_sheet/record_sheet_state.dart b/lib/components/record_sheet/record_sheet_state.dart new file mode 100644 index 0000000..0f42043 --- /dev/null +++ b/lib/components/record_sheet/record_sheet_state.dart @@ -0,0 +1,24 @@ +import 'package:get/get.dart'; + +class RecordSheetState { + late RxList amplitudes; + late Rx durationTime; + late RxBool isRecording; + late RxBool isStarted; + late String fileName; + late RxDouble height; + + late bool isStop; + + RecordSheetState() { + amplitudes = [].obs; + isRecording = false.obs; + isStarted = false.obs; + fileName = ''; + height = .0.obs; + isStop = false; + durationTime = const Duration().obs; + + ///Initialize variables + } +} diff --git a/lib/components/record_sheet/record_sheet_view.dart b/lib/components/record_sheet/record_sheet_view.dart new file mode 100644 index 0000000..fce1772 --- /dev/null +++ b/lib/components/record_sheet/record_sheet_view.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/wave_form/wave_form_view.dart'; + +import 'record_sheet_logic.dart'; + +class RecordSheetComponent extends StatelessWidget { + const RecordSheetComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(RecordSheetLogic()); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Obx(() { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 100), + child: state.isStarted.value + ? SizedBox( + height: 100, + key: const ValueKey('wave'), + child: Obx(() { + return WaveFormComponent( + amplitudes: state.amplitudes.value, + height: 100, + ); + }), + ) + : SizedBox( + height: 100, + key: const ValueKey('start'), + child: Center( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: colorScheme.outline, width: 4.0), shape: BoxShape.circle), + child: IconButton( + onPressed: () { + logic.startRecorder(); + }, + padding: EdgeInsets.zero, + icon: const Icon( + Icons.circle, + size: 48, + color: Colors.redAccent, + )), + )), + ), + ); + }), + Obx(() { + return AnimatedContainer( + duration: const Duration(milliseconds: 100), + height: state.height.value, + child: OverflowBox( + minHeight: 0, + maxHeight: state.height.value, + alignment: Alignment.center, + child: state.isStarted.value + ? Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + key: const ValueKey('playButton'), + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Obx(() { + return Text(state.durationTime.value.toString().split('.')[0]); + }), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: () { + logic.cancelRecorder(); + }, + child: const Text('取消')), + FilledButton( + onPressed: () { + state.isRecording.value ? logic.pauseRecorder() : logic.resumeRecorder(); + }, + child: AnimatedIcon( + icon: AnimatedIcons.play_pause, + progress: logic.animationController, + )), + TextButton( + onPressed: () { + logic.stopRecorder(); + }, + child: const Text('保存')), + ], + ) + ], + ) + : null, + ), + ); + }), + ], + ); + }, + ); + } +} diff --git a/lib/components/register_form/register_form_logic.dart b/lib/components/register_form/register_form_logic.dart new file mode 100644 index 0000000..ab7a90e --- /dev/null +++ b/lib/components/register_form/register_form_logic.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/login/login_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'register_form_state.dart'; + +class RegisterFormLogic extends GetxController { + final RegisterFormState state = RegisterFormState(); + + final FocusNode emailFocusNode = FocusNode(); + final FocusNode passwordFocusNode = FocusNode(); + final FocusNode rePasswordFocusNode = FocusNode(); + late final loginLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + + emailFocusNode.dispose(); + passwordFocusNode.dispose(); + rePasswordFocusNode.dispose(); + + super.onClose(); + } + + void unFocus() { + emailFocusNode.unfocus(); + passwordFocusNode.unfocus(); + rePasswordFocusNode.unfocus(); + } + + Future submit() async { + unFocus(); + if (state.formKey.currentState!.validate()) { + state.formKey.currentState!.save(); + await Utils().supabaseUtil.signUp(state.email, state.password).then((value) {}, onError: (_) { + Utils().noticeUtil.showToast('该账号已经注册'); + }); + } + } +} diff --git a/lib/components/register_form/register_form_state.dart b/lib/components/register_form/register_form_state.dart new file mode 100644 index 0000000..239ce9f --- /dev/null +++ b/lib/components/register_form/register_form_state.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class RegisterFormState { + final formKey = GlobalKey(); + + late String email; + late String password; + late String rePassword; + late String token; + + RegisterFormState() { + email = ''; + password = ''; + rePassword = ''; + token = ''; + + ///Initialize variables + } +} diff --git a/lib/components/register_form/register_form_view.dart b/lib/components/register_form/register_form_view.dart new file mode 100644 index 0000000..55e8852 --- /dev/null +++ b/lib/components/register_form/register_form_view.dart @@ -0,0 +1,132 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/function_extensions.dart'; + +import 'register_form_logic.dart'; + +class RegisterFormComponent extends StatelessWidget { + const RegisterFormComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(RegisterFormLogic()); + final state = Bind.find().state; + final size = MediaQuery.sizeOf(context); + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return GestureDetector( + onTap: () { + logic.unFocus(); + }, + behavior: HitTestBehavior.opaque, + child: Center( + child: SizedBox( + width: min(300, size.width / 1.618), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Form( + key: state.formKey, + child: Column( + children: [ + TextFormField( + onSaved: (value) { + state.email = value!; + }, + onChanged: (value) { + state.email = value; + }, + validator: (value) { + return (value == null || !GetUtils.isEmail(value)) ? '请输入邮箱' : null; + }, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: '邮箱', + ), + focusNode: logic.emailFocusNode, + ), + const SizedBox( + height: 20, + ), + TextFormField( + validator: (value) { + if (value!.isEmpty) { + return '请输入密码'; + } else if (value.length < 6) { + return '密码最少六位'; + } else { + return null; + } + }, + onSaved: (value) { + state.password = value!; + }, + onChanged: (value) { + state.password = value; + }, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: '密码', + ), + focusNode: logic.passwordFocusNode, + ), + const SizedBox( + height: 20, + ), + TextFormField( + validator: (value) { + if (value!.isEmpty) { + return '请输入密码'; + } else if (value != state.password) { + return '两次密码不一致'; + } else { + return null; + } + }, + onChanged: (value) { + state.rePassword = value; + }, + onSaved: (value) { + state.rePassword = value!; + }, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: '确认密码', + ), + focusNode: logic.rePasswordFocusNode, + ), + const SizedBox( + height: 20, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + logic.loginLogic.changeForm(); + }, + child: const Text("返回登录")), + ElevatedButton( + onPressed: () { + logic.submit(); + }.throttleWithTimeout(timeout: 5000), + child: const Icon(Icons.check), + ), + ], + ) + ], + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/remove_password/remove_password_logic.dart b/lib/components/remove_password/remove_password_logic.dart new file mode 100644 index 0000000..067d7ae --- /dev/null +++ b/lib/components/remove_password/remove_password_logic.dart @@ -0,0 +1,83 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/setting/setting_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'remove_password_state.dart'; + +class RemovePasswordLogic extends GetxController with GetSingleTickerProviderStateMixin { + final RemovePasswordState state = RemovePasswordState(); + late AnimationController animationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + late Animation animation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animationController, curve: Curves.easeInOut)); + + late final settingLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + animationController.dispose(); + super.onClose(); + } + + double interpolate(double x) { + var step = 10.0; + if (x <= 0.25) { + // 第一段: (0, step) - 单调递增 + return 4 * step * x; + } else if (x <= 0.75) { + // 第二段: (step, -step) - 单调递减 + return step - 4 * step * (x - 0.25); + } else { + // 第三段: (-step, 0) - 单调递增 + return -step + 4 * step * (x - 0.75); + } + } + + void deletePassword() { + if (state.password.value.isNotEmpty) { + state.password.value = state.password.value.substring(0, state.password.value.length - 1); + HapticFeedback.selectionClick(); + } + } + + Future updatePassword(String value) async { + if (state.password.value.length < 4) { + state.password.value += value; + HapticFeedback.selectionClick(); + } + Future.delayed(const Duration(milliseconds: 100), () async { + if (state.password.value.length == 4) { + //密码正确 + if (state.password.value == state.realPassword.value) { + await removePassword(); + } else { + animationController.forward(); + await HapticFeedback.mediumImpact(); + Future.delayed(const Duration(milliseconds: 200), () { + animationController.reverse(); + state.password.value = ''; + }); + } + } + }); + } + + Future removePassword() async { + //lock标记为false说明关闭密码 + await Utils().prefUtil.setValue('lock', false); + //移除密码字段 + await Utils().prefUtil.removeValue('password'); + settingLogic.state.lock.value = false; + Utils().noticeUtil.showToast('关闭成功'); + Get.backLegacy(); + } +} diff --git a/lib/components/remove_password/remove_password_state.dart b/lib/components/remove_password/remove_password_state.dart new file mode 100644 index 0000000..79c6cff --- /dev/null +++ b/lib/components/remove_password/remove_password_state.dart @@ -0,0 +1,15 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class RemovePasswordState { + late RxString password; + + late RxString realPassword; + + RemovePasswordState() { + password = ''.obs; + realPassword = Utils().prefUtil.getValue('password')!.obs; + + ///Initialize variables + } +} diff --git a/lib/components/remove_password/remove_password_view.dart b/lib/components/remove_password/remove_password_view.dart new file mode 100644 index 0000000..31d7a66 --- /dev/null +++ b/lib/components/remove_password/remove_password_view.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'remove_password_logic.dart'; + +class RemovePasswordComponent extends StatelessWidget { + const RemovePasswordComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(RemovePasswordLogic()); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + + final buttonSize = (textStyle.displayLarge!.fontSize! * textStyle.displayLarge!.height!); + Widget buildNumButton(String num) { + return Ink( + decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest, shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () async { + await logic.updatePassword(num); + }, + child: Center( + child: Text( + num, + style: textStyle.displaySmall, + )), + ), + ); + } + + Widget buildDeleteButton() { + return Ink( + decoration: const BoxDecoration(shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () { + logic.deletePassword(); + }, + child: const Icon( + Icons.backspace, + ), + ), + ); + } + + Widget buildBiometricsButton() { + return Ink( + decoration: const BoxDecoration(shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () async { + if (await Utils().authUtil.check()) { + logic.removePassword(); + } + }, + child: const Icon( + Icons.fingerprint, + ), + ), + ); + } + + List buildPasswordIndicator() { + return List.generate(4, (index) { + return Obx(() { + return Icon( + Icons.circle, + size: 16, + color: Color.lerp( + state.password.value.length > index ? colorScheme.onSurface : colorScheme.surfaceContainerHighest, + Colors.red, + logic.animation.value), + ); + }); + }); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '请输入密码', + style: textStyle.titleMedium, + ), + const SizedBox( + height: 16.0, + ), + AnimatedBuilder( + animation: logic.animation, + builder: (context, child) { + return Transform.translate( + offset: Offset(logic.interpolate(logic.animation.value), 0), + child: Wrap( + spacing: 16.0, + children: buildPasswordIndicator(), + ), + ); + }, + ), + const SizedBox( + height: 32.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: buttonSize * 3 + 20, + height: buttonSize * 4 + 30, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.0, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), + children: [ + buildNumButton('1'), + buildNumButton('2'), + buildNumButton('3'), + buildNumButton('4'), + buildNumButton('5'), + buildNumButton('6'), + buildNumButton('7'), + buildNumButton('8'), + buildNumButton('9'), + buildBiometricsButton(), + buildNumButton('0'), + buildDeleteButton() + ], + ), + ), + ], + ), + const SizedBox( + height: 16.0, + ), + ], + ); + }, + ); + } +} diff --git a/lib/components/search_card/search_card_logic.dart b/lib/components/search_card/search_card_logic.dart new file mode 100644 index 0000000..f3d10d9 --- /dev/null +++ b/lib/components/search_card/search_card_logic.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/router/app_routes.dart'; + +import 'search_card_state.dart'; + +class SearchCardLogic extends GetxController { + final SearchCardState state = SearchCardState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + //选中卡片后跳转到详情页,直接携带Diary作为参数 + Future toDiaryPage(Diary diary) async { + await Get.toNamed(AppRoutes.diaryPage, arguments: diary); + } +} diff --git a/lib/components/search_card/search_card_state.dart b/lib/components/search_card/search_card_state.dart new file mode 100644 index 0000000..2d8306c --- /dev/null +++ b/lib/components/search_card/search_card_state.dart @@ -0,0 +1,5 @@ +class SearchCardState { + SearchCardState() { + ///Initialize variables + } +} diff --git a/lib/components/search_card/search_card_view.dart b/lib/components/search_card/search_card_view.dart new file mode 100644 index 0000000..298032b --- /dev/null +++ b/lib/components/search_card/search_card_view.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; + +import 'search_card_logic.dart'; + +class SearchCardComponent extends StatelessWidget { + const SearchCardComponent({super.key, required this.diary, required this.index}); + + final Diary diary; + final String index; + + @override + Widget build(BuildContext context) { + final logic = Get.put(SearchCardLogic()); + + final colorScheme = Theme.of(context).colorScheme; + + return GetBuilder( + init: logic, + tag: index, + assignId: true, + builder: (logic) { + return InkWell( + onTap: () { + logic.toDiaryPage(diary); + }, + child: Container( + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, borderRadius: const BorderRadius.all(Radius.circular(8.0))), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + diary.contentText, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + Text(diary.time.toString().split('.')[0]) + ], + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/search_sheet/search_sheet_logic.dart b/lib/components/search_sheet/search_sheet_logic.dart new file mode 100644 index 0000000..143066b --- /dev/null +++ b/lib/components/search_sheet/search_sheet_logic.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'search_sheet_state.dart'; + +class SearchSheetLogic extends GetxController { + final SearchSheetState state = SearchSheetState(); + late TextEditingController textEditingController = TextEditingController(); + late FocusNode focusNode = FocusNode(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + textEditingController.dispose(); + focusNode.dispose(); + super.onClose(); + } + + void clearSearch() { + state.searchList = []; + update(); + } + + Future search() async { + focusNode.unfocus(); + if (textEditingController.text != '') { + state.isSearching.value = true; + state.searchList = await Utils().isarUtil.searchDiaries(textEditingController.text); + state.totalCount.value = state.searchList.length; + state.isSearching.value = false; + } + } +} diff --git a/lib/components/search_sheet/search_sheet_state.dart b/lib/components/search_sheet/search_sheet_state.dart new file mode 100644 index 0000000..4c31d6b --- /dev/null +++ b/lib/components/search_sheet/search_sheet_state.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; + +class SearchSheetState { + //日记搜索数组 + late List searchList; + + late RxBool isSearching; + + late RxInt totalCount; + + SearchSheetState() { + searchList = []; + isSearching = false.obs; + totalCount = 0.obs; + + ///Initialize variables + } +} diff --git a/lib/components/search_sheet/search_sheet_view.dart b/lib/components/search_sheet/search_sheet_view.dart new file mode 100644 index 0000000..2e65768 --- /dev/null +++ b/lib/components/search_sheet/search_sheet_view.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/search_card/search_card_view.dart'; + +import 'search_sheet_logic.dart'; + +class SearchSheetComponent extends StatelessWidget { + const SearchSheetComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(SearchSheetLogic()); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: TextField( + maxLines: 1, + controller: logic.textEditingController, + focusNode: logic.focusNode, + decoration: InputDecoration( + fillColor: colorScheme.secondaryContainer, + border: const UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide.none, + ), + filled: true, + labelText: '搜索', + suffixIcon: IconButton( + onPressed: () { + logic.search(); + }, + icon: const Icon(Icons.search), + ), + ), + ), + ), + const SizedBox( + height: 2.0, + ), + Obx(() { + return Expanded( + child: state.isSearching.value + ? const Center( + child: CircularProgressIndicator(), + ) + : ListView.builder( + itemCount: state.searchList.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0, bottom: 0.0), + child: SearchCardComponent( + diary: state.searchList[index], + index: index.toString(), + ), + ); + }), + ); + }), + Padding( + padding: const EdgeInsets.all(8.0), + child: Obx(() { + return Text('共有${state.totalCount.value}篇'); + }), + ) + ], + ); + }, + ); + } +} diff --git a/lib/components/set_password/set_password_logic.dart b/lib/components/set_password/set_password_logic.dart new file mode 100644 index 0000000..4d28d62 --- /dev/null +++ b/lib/components/set_password/set_password_logic.dart @@ -0,0 +1,98 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/setting/setting_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'set_password_state.dart'; + +class SetPasswordLogic extends GetxController with GetSingleTickerProviderStateMixin { + final SetPasswordState state = SetPasswordState(); + + late AnimationController animationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + late Animation animation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animationController, curve: Curves.easeInOut)); + + late final settingLogic = Bind.find(); + + @override + void onInit() { + // TODO: implement onInit + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + animationController.dispose(); + super.onClose(); + } + + double interpolate(double x) { + var step = 10.0; + if (x <= 0.25) { + // 第一段: (0, step) - 单调递增 + return 4 * step * x; + } else if (x <= 0.75) { + // 第二段: (step, -step) - 单调递减 + return step - 4 * step * (x - 0.25); + } else { + // 第三段: (-step, 0) - 单调递增 + return -step + 4 * step * (x - 0.75); + } + } + + void deletePassword() { + if (state.password.value.isNotEmpty) { + state.password.value = state.password.value.substring(0, state.password.value.length - 1); + HapticFeedback.selectionClick(); + } + } + + Future updatePassword(String value) async { + if (state.password.value.length < 4) { + state.password.value += value; + HapticFeedback.selectionClick(); + } + Future.delayed(const Duration(milliseconds: 100), () async { + //如果第一遍输入完成,保存记录后重新输入第二遍 + if (state.password.value.length == 4 && state.checkPassword.value.isEmpty) { + state.checkPassword.value = state.password.value; + state.password.value = ''; + } + //第二次输入,进行校验 + if (state.password.value.length == 4 && state.checkPassword.value.isNotEmpty) { + //两次输入一致 + if (state.password.value == state.checkPassword.value) { + await setPassword(); + } else { + animationController.forward(); + await HapticFeedback.mediumImpact(); + Future.delayed(const Duration(milliseconds: 200), () { + animationController.reverse(); + state.password.value = ''; + state.checkPassword.value = ''; + }); + } + } + }); + } + + Future setPassword() async { + //lock标记为true说明开启了密码 + await Utils().prefUtil.setValue('lock', true); + //设置密码字段 + await Utils().prefUtil.setValue('password', state.password.value); + settingLogic.state.lock.value = true; + Utils().noticeUtil.showToast('设置成功'); + Get.backLegacy(); + } +} diff --git a/lib/components/set_password/set_password_state.dart b/lib/components/set_password/set_password_state.dart new file mode 100644 index 0000000..18b3fc8 --- /dev/null +++ b/lib/components/set_password/set_password_state.dart @@ -0,0 +1,14 @@ +import 'package:get/get.dart'; + +class SetPasswordState { + late RxString password; + + late RxString checkPassword; + + SetPasswordState() { + password = ''.obs; + checkPassword = ''.obs; + + ///Initialize variables + } +} diff --git a/lib/components/set_password/set_password_view.dart b/lib/components/set_password/set_password_view.dart new file mode 100644 index 0000000..cd6f059 --- /dev/null +++ b/lib/components/set_password/set_password_view.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'set_password_logic.dart'; + +class SetPasswordComponent extends StatelessWidget { + const SetPasswordComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(SetPasswordLogic()); + final state = Bind.find().state; + + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + + final buttonSize = (textStyle.displayLarge!.fontSize! * textStyle.displayLarge!.height!); + Widget buildNumButton(String num) { + return Ink( + decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest, shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () async { + await logic.updatePassword(num); + }, + child: Center( + child: Text( + num, + style: textStyle.displaySmall, + )), + ), + ); + } + + Widget buildDeleteButton() { + return Ink( + decoration: const BoxDecoration(shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () { + logic.deletePassword(); + }, + child: const Icon( + Icons.backspace, + ), + ), + ); + } + + List buildPasswordIndicator() { + return List.generate(4, (index) { + return Obx(() { + return Icon( + Icons.circle, + size: 16, + color: Color.lerp( + state.password.value.length > index ? colorScheme.onSurface : colorScheme.surfaceContainerHighest, + Colors.red, + logic.animation.value), + ); + }); + }); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Obx(() { + return Text( + state.checkPassword.value.isEmpty ? '请输入密码' : '请确认密码', + style: textStyle.titleMedium, + ); + }), + const SizedBox( + height: 16.0, + ), + AnimatedBuilder( + animation: logic.animation, + builder: (context, child) { + return Transform.translate( + offset: Offset(logic.interpolate(logic.animation.value), 0), + child: Wrap( + spacing: 16.0, + children: buildPasswordIndicator(), + ), + ); + }, + ), + const SizedBox( + height: 32.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: buttonSize * 3 + 20, + height: buttonSize * 4 + 30, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.0, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: [ + buildNumButton('1'), + buildNumButton('2'), + buildNumButton('3'), + buildNumButton('4'), + buildNumButton('5'), + buildNumButton('6'), + buildNumButton('7'), + buildNumButton('8'), + buildNumButton('9'), + const SizedBox(), + buildNumButton('0'), + buildDeleteButton() + ], + ), + ), + ], + ), + const SizedBox( + height: 16.0, + ), + ], + ); + }, + ); + } +} diff --git a/lib/components/side_bar/side_bar_logic.dart b/lib/components/side_bar/side_bar_logic.dart new file mode 100644 index 0000000..64ba365 --- /dev/null +++ b/lib/components/side_bar/side_bar_logic.dart @@ -0,0 +1,91 @@ +import 'dart:async'; + +import 'package:get/get.dart'; +import 'package:mood_diary/api/api.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'side_bar_state.dart'; + +class SideBarLogic extends GetxController { + final SideBarState state = SideBarState(); + + late HomeLogic homeLogic = Bind.find(); + + @override + void onInit() { + // TODO: implement onInit + getHitokoto(); + getImage(); + getInfo(); + getWeather(); + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + Future getWeather() async { + var key = Utils().prefUtil.getValue('qweatherKey'); + if (state.getWeather && key != null) { + state.weatherResponse.value = + await Utils().cacheUtil.getCacheList('weather', Api().updateWeather, maxAgeMillis: 15 * 60000) ?? []; + } + } + + Future getHitokoto() async { + var res = await Utils().cacheUtil.getCacheList('hitokoto', Api().updateHitokoto, maxAgeMillis: 15 * 60000); + if (res != null) { + state.hitokoto.value = res.first; + } + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future getImage() async { + var url = await Utils().cacheUtil.getCacheList('bingImage', Api().updateImageUrl, maxAgeMillis: 6 * 60 * 60000); + if (url != null) { + state.imageUrl.value = url.first; + } + } + + Future getInfo() async { + state.packageInfo.value = await Utils().packageUtil.getPackageInfo(); + } + + //跳转到反馈页 + Future toReportPage() async { + var uri = Uri(scheme: 'https', host: 'support.qq.com', path: 'products/650147', queryParameters: { + 'nickname': Utils().prefUtil.getValue('uuid'), + 'avatar': 'https://txc.qq.com/static/desktop/img/products/def-product-logo.png', + 'openid': Utils().prefUtil.getValue('uuid') + }); + await launchUrl(uri, mode: LaunchMode.platformDefault); + } + + Future toSettingPage() async { + Get.backLegacy(); + //进入设置页 + await Get.toNamed(AppRoutes.settingPage); + //返回后刷新 + await homeLogic.updateDiary(); + } + + void toPrivacy() { + Get.toNamed(AppRoutes.privacyPage); + } + + void toAssistant() { + Get.toNamed(AppRoutes.assistantPage); + } +} diff --git a/lib/components/side_bar/side_bar_state.dart b/lib/components/side_bar/side_bar_state.dart new file mode 100644 index 0000000..f8854da --- /dev/null +++ b/lib/components/side_bar/side_bar_state.dart @@ -0,0 +1,30 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class SideBarState { + var packageInfo = Rx(null); + + late RxString hitokoto; + + late Diary diary; + late DateTime nowTime; + late RxString imageUrl; + + //天气 + late RxList weatherResponse; + + late bool getWeather; + + SideBarState() { + //获取信息 + hitokoto = ''.obs; + nowTime = DateTime.now(); + imageUrl = ''.obs; + weatherResponse = [].obs; + getWeather = Utils().prefUtil.getValue('getWeather')!; + + ///Initialize variables + } +} diff --git a/lib/components/side_bar/side_bar_view.dart b/lib/components/side_bar/side_bar_view.dart new file mode 100644 index 0000000..9120e64 --- /dev/null +++ b/lib/components/side_bar/side_bar_view.dart @@ -0,0 +1,229 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:mood_diary/common/values/icons.dart'; +import 'package:mood_diary/components/update_dialog/update_dialog_view.dart'; +import 'package:mood_diary/utils/function_extensions.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'side_bar_logic.dart'; + +class SideBarComponent extends StatelessWidget { + const SideBarComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(SideBarLogic()); + final state = Bind.find().state; + final textStyle = Theme.of(context).textTheme; + final i18n = AppLocalizations.of(context)!; + Widget buildWeather() { + return Column( + children: [ + Icon( + WeatherIcon.map[state.weatherResponse[0]]!, + color: Colors.white, + shadows: const [ + Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ], + ), + Text( + '${state.weatherResponse[2]} ${state.weatherResponse[1]}°C', + style: textStyle.titleMedium!.copyWith(color: Colors.white, shadows: [ + const Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ]), + ) + ], + ); + } + + Widget buildHitokoto() { + return Center( + child: Obx(() { + return Text( + state.hitokoto.value, + style: textStyle.titleMedium!.copyWith(color: Colors.white, shadows: [ + const Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ]), + overflow: TextOverflow.fade, + ); + }), + ); + } + + Widget buildDate() { + return Row( + children: [ + Text( + state.nowTime.day.toString(), + style: textStyle.displayMedium!.copyWith(color: Colors.white, shadows: [ + const Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ]), + ), + const SizedBox( + width: 8.0, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + DateFormat.MMM().format(state.nowTime), + style: textStyle.titleSmall!.copyWith(color: Colors.white, shadows: [ + const Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ]), + ), + Text( + DateFormat.EEEE().format(state.nowTime), + style: textStyle.titleMedium!.copyWith(color: Colors.white, shadows: [ + const Shadow( + offset: Offset(2.0, 2.0), + blurRadius: 8.0, + color: Colors.black38, + ), + ]), + ) + ], + ), + ], + ); + } + + Widget buildAction() { + return OverflowBar( + children: [ + TextButton.icon( + onPressed: () async { + await logic.toSettingPage(); + }, + label: const Text('设置'), + icon: const Icon(Icons.settings), + ) + ], + ); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Drawer( + child: Column( + children: [ + Obx(() { + return DrawerHeader( + decoration: BoxDecoration( + image: state.imageUrl.value.isNotEmpty + ? DecorationImage( + image: CachedNetworkImageProvider(state.imageUrl.value), + fit: BoxFit.cover, + ) + : null), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildDate(), + if (state.getWeather && state.weatherResponse.value.isNotEmpty) ...[buildWeather()] + ], + ), + Expanded(child: buildHitokoto()), + ], + ), + ); + }), + Expanded( + child: ListView( + physics: const ClampingScrollPhysics(), + padding: EdgeInsets.zero, + children: [ + Obx(() { + return AboutListTile( + icon: const Icon(Icons.info_outline), + applicationName: state.packageInfo.value != null ? state.packageInfo.value!.appName : '', + applicationVersion: state.packageInfo.value != null + ? 'v${state.packageInfo.value!.version}(${state.packageInfo.value!.buildNumber})' + : '', + applicationLegalese: '\u{a9} 2024 江有汜', + aboutBoxChildren: const [ + Padding( + padding: EdgeInsets.fromLTRB(24.0, 0, 24.0, 0), + child: Column( + children: [], + ), + ) + ], + child: Text(i18n.sidebarAbout), + ); + }), + ListTile( + leading: const Icon(Icons.security_outlined), + onTap: () { + logic.toPrivacy(); + }, + title: Text( + i18n.sidebarPrivacy, + ), + ), + ListTile( + leading: const Icon(Icons.bug_report_outlined), + onTap: () { + logic.toReportPage(); + }, + title: Text( + i18n.sidebarBug, + ), + ), + ListTile( + leading: const Icon(Icons.update), + onTap: () async { + if (Platform.isAndroid) { + var res = await Utils().updateUtil.checkUpdate(); + if (res != null && context.mounted) { + showDialog( + context: context, + builder: (context) { + return UpdateDialogComponent(shiplyResponse: res); + }); + } else { + Utils().noticeUtil.showToast('已是最新版本'); + } + } + }.throttleWithTimeout(timeout: 3000), + title: Text(i18n.sidebarCheckUpdate), + ), + ], + ), + ), + buildAction() + ], + ), + ); + }, + ); + } +} diff --git a/lib/components/theme_mode_dialog/theme_mode_dialog_logic.dart b/lib/components/theme_mode_dialog/theme_mode_dialog_logic.dart new file mode 100644 index 0000000..576a0d3 --- /dev/null +++ b/lib/components/theme_mode_dialog/theme_mode_dialog_logic.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/setting/setting_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'theme_mode_dialog_state.dart'; + +class ThemeModeDialogLogic extends GetxController { + final ThemeModeDialogState state = ThemeModeDialogState(); + late final settingLogic = Bind.find(); + + @override + void onInit() { + // TODO: implement onInit + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + //修改颜色模式 + Future changeThemeMode(int value) async { + await Utils().prefUtil.setValue('themeMode', value); + state.themeMode = value; + settingLogic.state.themeMode.value = value; + Get.changeThemeMode(ThemeMode.values[value]); + Get.forceAppUpdate(); + } +} diff --git a/lib/components/theme_mode_dialog/theme_mode_dialog_state.dart b/lib/components/theme_mode_dialog/theme_mode_dialog_state.dart new file mode 100644 index 0000000..8ee2552 --- /dev/null +++ b/lib/components/theme_mode_dialog/theme_mode_dialog_state.dart @@ -0,0 +1,11 @@ +import 'package:mood_diary/utils/utils.dart'; + +class ThemeModeDialogState { + late int themeMode; + + ThemeModeDialogState() { + themeMode = (Utils().prefUtil.getValue('themeMode'))!; + + ///Initialize variables + } +} diff --git a/lib/components/theme_mode_dialog/theme_mode_dialog_view.dart b/lib/components/theme_mode_dialog/theme_mode_dialog_view.dart new file mode 100644 index 0000000..eb47585 --- /dev/null +++ b/lib/components/theme_mode_dialog/theme_mode_dialog_view.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; + +import 'theme_mode_dialog_logic.dart'; + +class ThemeModeDialogComponent extends StatelessWidget { + const ThemeModeDialogComponent({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Get.put(ThemeModeDialogLogic()); + final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return SimpleDialog( + title: Text(i18n.settingThemeMode), + children: [ + SimpleDialogOption( + child: Row( + children: [ + if (state.themeMode == 0) ...[ + const Icon(Icons.check), + ] else ...[ + const Icon(Icons.brightness_auto_outlined), + ], + const SizedBox( + width: 10, + ), + Text(i18n.themeModeSystem), + ], + ), + onPressed: () { + logic.changeThemeMode(0); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + if (state.themeMode == 1) ...[ + const Icon(Icons.check), + ] else ...[ + const Icon(Icons.light_mode_outlined), + ], + const SizedBox( + width: 10, + ), + Text(i18n.themeModeLight), + ], + ), + onPressed: () { + logic.changeThemeMode(1); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + if (state.themeMode == 2) ...[ + const Icon(Icons.check), + ] else ...[ + const Icon(Icons.dark_mode_outlined), + ], + const SizedBox( + width: 10, + ), + Text(i18n.themeModeDark), + ], + ), + onPressed: () { + logic.changeThemeMode(2); + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/components/update_dialog/update_dialog_logic.dart b/lib/components/update_dialog/update_dialog_logic.dart new file mode 100644 index 0000000..7ae510a --- /dev/null +++ b/lib/components/update_dialog/update_dialog_logic.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; + +import 'update_dialog_state.dart'; + +class UpdateDialogLogic extends GetxController { + final UpdateDialogState state = UpdateDialogState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/components/update_dialog/update_dialog_state.dart b/lib/components/update_dialog/update_dialog_state.dart new file mode 100644 index 0000000..4a037c6 --- /dev/null +++ b/lib/components/update_dialog/update_dialog_state.dart @@ -0,0 +1,5 @@ +class UpdateDialogState { + UpdateDialogState() { + ///Initialize variables + } +} diff --git a/lib/components/update_dialog/update_dialog_view.dart b/lib/components/update_dialog/update_dialog_view.dart new file mode 100644 index 0000000..e267752 --- /dev/null +++ b/lib/components/update_dialog/update_dialog_view.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/shiply.dart'; +import 'package:mood_diary/utils/channel.dart'; + +class UpdateDialogComponent extends StatelessWidget { + const UpdateDialogComponent({super.key, required this.shiplyResponse}); + + final ShiplyResponse shiplyResponse; + + @override + Widget build(BuildContext context) { + // final logic = Get.put(UpdateDialogLogic()); + // final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + final colorScheme = Theme.of(context).colorScheme; + return AlertDialog( + title: Wrap( + spacing: 8.0, + children: [ + Text(shiplyResponse.clientInfo!.title!), + Chip( + label: Text( + 'V${shiplyResponse.apkBasicInfo!.version!}', + style: TextStyle(color: colorScheme.onTertiaryContainer), + ), + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + side: BorderSide.none, + backgroundColor: colorScheme.tertiaryContainer, + ), + ], + ), + content: Text(shiplyResponse.clientInfo!.description!), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + FilledButton( + onPressed: () async { + Get.backLegacy(); + await ShiplyChannel.startDownload(); + }, + child: const Text('立即更新'), + ), + ], + ); + } +} diff --git a/lib/components/wave_form/wave_form_logic.dart b/lib/components/wave_form/wave_form_logic.dart new file mode 100644 index 0000000..784e8d9 --- /dev/null +++ b/lib/components/wave_form/wave_form_logic.dart @@ -0,0 +1,20 @@ +import 'package:get/get.dart'; + +import 'wave_form_state.dart'; + +class WaveFormLogic extends GetxController with GetSingleTickerProviderStateMixin { + final WaveFormState state = WaveFormState(); + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/components/wave_form/wave_form_state.dart b/lib/components/wave_form/wave_form_state.dart new file mode 100644 index 0000000..41aed53 --- /dev/null +++ b/lib/components/wave_form/wave_form_state.dart @@ -0,0 +1,5 @@ +class WaveFormState { + WaveFormState() { + ///Initialize variables + } +} diff --git a/lib/components/wave_form/wave_form_view.dart b/lib/components/wave_form/wave_form_view.dart new file mode 100644 index 0000000..9d23530 --- /dev/null +++ b/lib/components/wave_form/wave_form_view.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/record_sheet/record_sheet_logic.dart'; + +class WaveFormPainter extends CustomPainter { + final double barWidth; + final double spaceWidth; + final Color color; + final List amplitudes; + + final recordLogic = Bind.find(); + + WaveFormPainter( + this.amplitudes, { + required this.barWidth, + required this.spaceWidth, + this.color = Colors.white, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..strokeCap = StrokeCap.round + ..strokeWidth = barWidth + ..style = PaintingStyle.fill; + + final barHeightFactor = size.height - barWidth; + for (int i = 0; i < amplitudes.length; i++) { + final x = i * (barWidth + spaceWidth) + barWidth / 2; + final y = barHeightFactor * (1 - amplitudes[i]); + canvas.drawLine(Offset(x, barHeightFactor), Offset(x, y), paint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +class WaveFormComponent extends StatelessWidget { + const WaveFormComponent({ + super.key, + required this.amplitudes, + this.barWidth = 4.0, + this.spaceWidth = 2.0, + required this.height, + }); + + final List amplitudes; + final double barWidth; + final double spaceWidth; + final double height; + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return CustomPaint( + painter: WaveFormPainter(amplitudes, color: colorScheme.primary, barWidth: barWidth, spaceWidth: spaceWidth), + size: Size(amplitudes.length * (barWidth + spaceWidth), height), + ); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb new file mode 100644 index 0000000..b2c9189 --- /dev/null +++ b/lib/l10n/intl_en.arb @@ -0,0 +1,100 @@ +{ + "@@locale": "en", + "ok": "OK", + "cancel": "Cancel", + "appName": "Moodiary", + "greetingNight": "It's late,", + "greetingMorning": "Good morning,", + "greetingForenoon": "Good forenoon,", + "greetingNoon": "Good noon,", + "greetingAfternoon": "Good afternoon,", + "greetingEvening": "It's evening,", + "greetingNight2": "Good night,", + "detailNight": "Get some rest!", + "detailMorning": "Have a great day.", + "detailForenoon": "Hope everything goes well.", + "detailNoon": "Take a nap, it's good for health.", + "detailAfternoon": "How much did you achieve today?", + "detailEvening": "Enjoy the sunset.", + "detailNight2": "How was your day?", + "dayOne": "Mon.", + "dayTwo": "Tue.", + "dayThree": "Wed.", + "dayFour": "Thu.", + "dayFive": "Fri.", + "daySix": "Sat.", + "daySeven": "Sun.", + "startTitle1": "Welcome to the ", + "startTitle2": "Mood Diary", + "startTitle3": "An ad-free, social-free, intimate diary", + "welcome1": "Thanks for downloading this product! Before use, we hope you can read and understand our ", + "welcome2": "Privacy Policy ", + "welcome3": "and ", + "welcome4": "User Agreement", + "welcome5": ". We always respect and will strictly protect your legitimate rights and interests when using this product from any infringement. If you begin to use this product, you will be deemed to have accepted this Agreement. If you do not accept all the terms of this Agreement, do not begin to use this Product.", + "startChoice1": "Exit", + "startChoice2": "Start", + "permission1": "Permission grant", + "permission2": "In order to better use the experience, we need the following permissions", + "permission3": "• Location permission (for getting the weather)", + "shareTitle": "Share", + "shareName": "\u00A9 Mood Diary", + "settingTitle": "Settings", + "settingData": "Data", + "settingRecycle": "Recycle Bin", + "settingExport": "Export", + "settingExportDialogTitle": "Data Export", + "settingExportDialogContent": "After confirmation, the current application's data will be exported as a ZIP file, which can be used for intra application import.", + "settingImport": "Import", + "settingImportDialogTitle": "Data Import", + "settingImportDialogContent": "Importing data will overwrite the existing data and the original data cannot be restored! Please confirm that the original data has been backed up.", + "settingImportSelectFile": "Select File", + "settingImportDes": "Only supports files exported from this app", + "settingClean": "Clear Cache", + "settingDisplay": "Display and Personalization", + "settingThemeMode": "Theme Mode", + "settingColor": "Color Scheme", + "settingAutoPlay": "Homepage Card Auto-Play", + "settingDynamicColor": "Homepage Card Dynamic Color", + "settingImageQuality": "Image Quality", + "settingImageQualityDes": "Only applies to modified images", + "settingFontSize": "Font Size", + "settingFontStyle": "Font Style", + "settingWeather": "Show Weather on Homepage", + "settingPrivacy": "Privacy and Security", + "settingLocal": "Localization", + "settingLocalDes": "Turn off all cloud features when enabled", + "settingLock": "Password", + "settingLockSupportBiometricsDes": "System supports biometrics", + "settingLockNotSupportBiometricsDes": "System does not support biometrics", + "settingLockOpen": "Open", + "settingLockNotOpen": "Not Open", + "settingLockNow": "Lock Now", + "settingLockNowDes": "Lock the app immediately upon leaving", + "settingLockChooseLockType": "Please select a password type", + "settingLockResetLock": "Password has been enabled, reset please close first", + "settingMore": "More", + "settingLab": "Laboratory", + "themeModeSystem": "System Mode", + "themeModeLight": "Light Mode", + "themeModeDark": "Dark Mode", + "colorNameSystem": "System Color", + "colorNameBaiCaoShuang": "Herb frost", + "colorNameZhuYue": "Bamboo moon", + "colorNameLvLiuLi": "Green glaze", + "colorNameJin": "Hibiscus", + "colorNameShiYangJin": "Brocade", + "fontNameDefault": "Default", + "qualityLow": "Low(720p)", + "qualityMedium": "Medium(1080p)", + "qualityHigh": "High(1440p)", + "lockEnterPassword": "Please enter the password", + "lockSetPassword": "Please set a password", + "sidebarUpdateLog": "Update Logs", + "sidebarAbout": "About Applications", + "sidebarPrivacy": "Privacy Policy", + "sidebarBug": "Bug Feedback", + "sidebarCheckUpdate": "Check For Updates", + "homeViewModeList": "List view", + "homeViewModeCalendar": "Calendar view" +} \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb new file mode 100644 index 0000000..7c8de00 --- /dev/null +++ b/lib/l10n/intl_zh.arb @@ -0,0 +1,100 @@ +{ + "@@locale": "zh", + "ok": "确认", + "cancel": "取消", + "appName": "心绪日记", + "greetingNight": "夜深了,", + "greetingMorning": "早上好,", + "greetingForenoon": "上午好,", + "greetingNoon": "中午好,", + "greetingAfternoon": "下午好,", + "greetingEvening": "傍晚了,", + "greetingNight2": "晚上好,", + "detailNight": "早点休息吧!", + "detailMorning": "祝你今天愉快。", + "detailForenoon": "希望你顺利。", + "detailNoon": "午休有益健康。", + "detailAfternoon": "今天收获了多少呢?", + "detailEvening": "看看今天的夕阳。", + "detailNight2": "今天过的怎么样?", + "dayOne": "周一", + "dayTwo": "周二", + "dayThree": "周三", + "dayFour": "周四", + "dayFive": "周五", + "daySix": "周六", + "daySeven": "周日", + "startTitle1": "欢迎使用", + "startTitle2": "心绪日记", + "startTitle3": "无广告、无社交的私密日记本", + "welcome1": "感谢下载本产品!在正式使用前,希望您能阅读并理解我们的", + "welcome2": "《隐私政策》", + "welcome3": "和", + "welcome4": "《用户协议》", + "welcome5": "。我们一向尊重并会严格保护您在使用本产品时的合法权益不受到任何侵犯。用户开始使用本产品将视为已经接受本协议,如果您不能接受本协议中的全部条款,请勿开始使用本产品。", + "startChoice1": "退出", + "startChoice2": "开始", + "permission1": "权限授予", + "permission2": "为了更好的使用体验,我们需要以下权限", + "permission3": "• 定位权限(用于获取天气)", + "shareTitle": "分享", + "shareName": "\u00A9 心绪日记", + "settingTitle": "设置", + "settingData": "数据", + "settingRecycle": "回收站", + "settingExport": "导出", + "settingExportDialogTitle": "数据导出", + "settingExportDialogContent": "确认后会将当前应用的数据导出为 ZIP 文件,文件可用于应用内导入使用。", + "settingImport": "导入", + "settingImportDialogTitle": "数据导入", + "settingImportDialogContent": "导入数据会覆盖当前已经有的数据,且原有数据无法恢复!请确认备份好原有数据。", + "settingImportSelectFile": "选择文件", + "settingImportDes": "仅支持本应用导出的文件", + "settingClean": "清理缓存", + "settingDisplay": "显示与个性", + "settingThemeMode": "主题模式", + "settingColor": "配色方案", + "settingAutoPlay": "首页卡片自动轮播", + "settingDynamicColor": "首页卡片动态配色", + "settingImageQuality": "图片质量", + "settingImageQualityDes": "只对修改后的图片生效", + "settingFontSize": "字体大小", + "settingFontStyle": "字体样式", + "settingWeather": "侧边栏显示天气", + "settingPrivacy": "隐私与安全", + "settingLocal": "本地化", + "settingLocalDes": "开启后关闭所有云端功能", + "settingLock": "密码", + "settingLockSupportBiometricsDes": "系统支持生物识别", + "settingLockNotSupportBiometricsDes": "系统不支持生物识别", + "settingLockOpen": "已开启", + "settingLockNotOpen": "未开启", + "settingLockNow": "立即锁定", + "settingLockNowDes": "离开应用时立即锁定应用", + "settingLockChooseLockType": "请选择密码类型", + "settingLockResetLock": "已经开启密码,重新设置请先关闭", + "settingMore": "更多", + "settingLab": "实验室", + "themeModeSystem": "跟随系统", + "themeModeLight": "浅色模式", + "themeModeDark": "深色模式", + "colorNameSystem": "系统颜色", + "colorNameBaiCaoShuang": "百草霜", + "colorNameZhuYue": "竹月", + "colorNameLvLiuLi": "绿琉璃", + "colorNameJin": "槿", + "colorNameShiYangJin": "十样锦", + "fontNameDefault": "默认", + "qualityLow": "低(720p)", + "qualityMedium": "中(1080p)", + "qualityHigh": "高(1440p)", + "lockEnterPassword": "请输入密码", + "lockSetPassword": "请设置密码", + "sidebarUpdateLog": "更新日志", + "sidebarAbout": "关于应用", + "sidebarPrivacy": "隐私政策", + "sidebarBug": "BUG反馈", + "sidebarCheckUpdate": "检查更新", + "homeViewModeList": "列表视图", + "homeViewModeCalendar": "日历视图" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..882a6b7 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,75 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl_standalone.dart'; +import 'package:mood_diary/router/app_pages.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/channel.dart'; +import 'package:mood_diary/utils/utils.dart'; + +Future initSystem() async { + //获取系统语言 + await findSystemLocale(); + //初始化pref + await Utils().prefUtil.initPref(); + //初始化所需目录 + await Utils().fileUtil.initCreateDir(); + //初始化Isar + await Utils().isarUtil.initIsar(); + //supabase初始化 + //await Utils().supabaseUtil.initSupabase(); + if (Platform.isAndroid) { + //shiply初始化 + await Utils().updateUtil.initShiply(); + //设置高刷 + await FlutterDisplayMode.setHighRefreshRate(); + //设置状态栏沉浸 + await ViewChannel.setSystemUIVisibility(); + } +} + +String getInitialRoute() { + var initialRoute = AppRoutes.homePage; + if (Utils().prefUtil.getValue('firstStart')!) { + initialRoute = AppRoutes.startPage; + } + if (Utils().prefUtil.getValue('lock')!) { + initialRoute = AppRoutes.lockPage; + } + return initialRoute; +} + +void main() { + runZonedGuarded(() async { + WidgetsFlutterBinding.ensureInitialized(); + FlutterError.onError = (details) { + Utils().logUtil.printError('Flutter error', error: details.exception, stackTrace: details.stack); + Utils().noticeUtil.showBug('Flutter error', error: details.exception, stackTrace: details.stack); + }; + //初始化系统 + await initSystem(); + runApp(GetMaterialApp( + initialRoute: getInitialRoute(), + builder: (context, widget) => MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.linear(Utils().prefUtil.getValue('fontScale')!), + ), + child: widget!), + theme: await Utils().themeUtil.buildTheme(Brightness.light), + darkTheme: await Utils().themeUtil.buildTheme(Brightness.dark), + themeMode: ThemeMode.values[Utils().prefUtil.getValue('themeMode')!], + debugShowCheckedModeBanner: false, + defaultTransition: Transition.cupertino, + getPages: AppPages.routes, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + )); + }, (error, stack) { + Utils().logUtil.printWTF('Error', error: error, stackTrace: stack); + Utils().noticeUtil.showBug('Error', error: error, stackTrace: stack); + }); +} diff --git a/lib/pages/agreement/agreement_logic.dart b/lib/pages/agreement/agreement_logic.dart new file mode 100644 index 0000000..727ccbf --- /dev/null +++ b/lib/pages/agreement/agreement_logic.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; + +import 'agreement_state.dart'; + +class AgreementLogic extends GetxController { + final AgreementState state = AgreementState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/pages/agreement/agreement_state.dart b/lib/pages/agreement/agreement_state.dart new file mode 100644 index 0000000..68562c6 --- /dev/null +++ b/lib/pages/agreement/agreement_state.dart @@ -0,0 +1,5 @@ +class AgreementState { + AgreementState() { + ///Initialize variables + } +} diff --git a/lib/pages/agreement/agreement_view.dart b/lib/pages/agreement/agreement_view.dart new file mode 100644 index 0000000..e74bae6 --- /dev/null +++ b/lib/pages/agreement/agreement_view.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:markdown_widget/markdown_widget.dart'; + +class AgreementPage extends StatelessWidget { + const AgreementPage({super.key}); + + @override + Widget build(BuildContext context) { + // final logic = Bind.find(); + // final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + return Scaffold( + appBar: AppBar(title: const Text('用户协议')), + body: MarkdownWidget( + data: '''# 用户协议 + +*江有汜*(以下简称“我们”)依据本协议为用户(以下简称“你”)提供*心绪日记*服务。本协议对你和我们均具有法律约束力。 + +#### 一、本服务的功能 + +你可以使用本服务撰写日记。 + +#### 二、责任范围及限制 + +你使用本服务得到的结果仅供参考,实际情况以官方为准。 + +#### 三、隐私保护 + +我们重视对你隐私的保护,你的个人隐私信息将根据《隐私政策》受到保护与规范,详情请参阅《隐私政策》。 + +#### 四、其他条款 + +4.1 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。 + +4.2 本协议条款无论因何种原因部分无效或不可执行,其余条款仍有效,对双方具有约束力。''', + selectable: true, + padding: const EdgeInsets.symmetric(horizontal: 10.0), + config: colorScheme.brightness == Brightness.dark ? MarkdownConfig.darkConfig : MarkdownConfig.defaultConfig, + ), + ); + } +} diff --git a/lib/pages/analyse/analyse_logic.dart b/lib/pages/analyse/analyse_logic.dart new file mode 100644 index 0000000..c550c8d --- /dev/null +++ b/lib/pages/analyse/analyse_logic.dart @@ -0,0 +1,106 @@ +import 'dart:convert'; + +import 'package:calendar_date_picker2/calendar_date_picker2.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/api/api.dart'; +import 'package:mood_diary/common/models/hunyuan.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'analyse_state.dart'; + +class AnalyseLogic extends GetxController { + final AnalyseState state = AnalyseState(); + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onInit() { + // TODO: implement onInit + getMoodAndWeatherByRange(state.dateRange[0], state.dateRange[1]); + super.onInit(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + //选中两个日期后,查询指定范围内的日记 + Future getMoodAndWeatherByRange(DateTime start, DateTime end) async { + //清空原有数据 + clearResult(); + //获取数据开始 + state.finished = false; + update(); + state.moodList = await Utils().isarUtil.getMoodByDateRange(start, end.subtract(const Duration(days: -1))); + var weatherList = await Utils().isarUtil.getWeatherByDateRange(start, end.subtract(const Duration(days: -1))); + Utils().logUtil.printInfo(weatherList); + for (var weather in weatherList) { + if (weather.isNotEmpty) { + state.weatherList.add(weather.first); + } + } + state.moodMap = Utils().arrayUtil.countList(state.moodList); + state.weatherMap = Utils().arrayUtil.countList(state.weatherList); + state.finished = true; + update(); + } + + void clearResult() { + state.moodList.clear(); + state.weatherList.clear(); + state.moodMap.clear(); + state.weatherMap.clear(); + } + + //弹出日期选择框 + Future openDatePicker(context) async { + var result = await showCalendarDatePicker2Dialog( + context: context, + config: CalendarDatePicker2WithActionButtonsConfig( + calendarViewMode: CalendarDatePicker2Mode.day, + calendarType: CalendarDatePicker2Type.range, + selectableDayPredicate: (date) => date.isBefore(DateTime.now()), + ), + dialogSize: const Size(325, 400), + value: state.dateRange, + borderRadius: BorderRadius.circular(20.0)); + if (result != null) { + state.dateRange[0] = result[0]!; + state.dateRange[1] = result[1]!; + update(); + getMoodAndWeatherByRange(result[0]!, result[1]!); + } + } + + Future getAi() async { + var check = Utils().signatureUtil.checkTencent(); + if (check != null) { + state.reply = ''; + update(); + var stream = await Api().getHunYuan( + check['id']!, + check['key']!, + [ + Message('system', + '我会给你一组来自一款日记APP的数据,其中包含了在某一段时间内,日记所记录的心情和天气情况,根据这些数据,给出我一条建议用户如何改善心情,回答稍微带一点文艺感,总字数不要超过200字,不需要任何其他反馈。'), + Message('user', '心情:${state.moodMap.toString()},天气:${state.weatherMap.toString()}') + ], + 0); + stream?.listen((content) { + if (content != '' && content.contains('data')) { + HunyuanResponse result = HunyuanResponse.fromJson(jsonDecode(content.split('data: ')[1])); + state.reply += result.choices!.first.delta!.content!; + update(); + } + }); + } + } +} diff --git a/lib/pages/analyse/analyse_state.dart b/lib/pages/analyse/analyse_state.dart new file mode 100644 index 0000000..9650a5c --- /dev/null +++ b/lib/pages/analyse/analyse_state.dart @@ -0,0 +1,33 @@ +class AnalyseState { + //当前选中的日期范围,默认为今天起一周 + late List dateRange; + + //统计范围内的心情列表 + late List moodList; + + //天气 + late List weatherList; + + //统计范围内日记的心情出现的次数 + late Map moodMap; + + late Map weatherMap; + + //加载状态,检查数据是否获取完成 + late bool finished; + + late String reply; + + AnalyseState() { + var now = DateTime.now().copyWith(hour: 0, minute: 0, second: 0); + reply = ''; + finished = false; + dateRange = [now.subtract(const Duration(days: 30)), now]; + moodList = []; + weatherList = []; + moodMap = {}; + weatherMap = {}; + + ///Initialize variables + } +} diff --git a/lib/pages/analyse/analyse_view.dart b/lib/pages/analyse/analyse_view.dart new file mode 100644 index 0000000..1caa803 --- /dev/null +++ b/lib/pages/analyse/analyse_view.dart @@ -0,0 +1,218 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/icons.dart'; +import 'package:mood_diary/components/mood_icon/mood_icon_view.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'analyse_logic.dart'; + +class AnalysePage extends StatelessWidget { + const AnalysePage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final textStyle = Theme.of(context).textTheme; + final colorScheme = Theme.of(context).colorScheme; + + //柱状图 + Widget buildBarChart( + String title, Map iconMap, Map countMap, List itemList) { + //去重 + itemList = Utils().arrayUtil.toSetList(itemList); + return Container( + padding: const EdgeInsets.all(20.0), + margin: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + decoration: + BoxDecoration(color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(10.0)), + child: Column( + children: [ + Text( + title, + style: textStyle.titleMedium!.copyWith(color: colorScheme.onSurface), + ), + const SizedBox( + height: 10, + ), + AspectRatio( + aspectRatio: 1.0, + child: itemList.isNotEmpty + ? BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + borderData: FlBorderData( + show: true, + border: Border.symmetric( + horizontal: BorderSide( + color: colorScheme.onSurface.withOpacity(0.6), + ), + ), + ), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1.0, + getTitlesWidget: (value, meta) { + return Text(value.toInt().toString()); + })), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return SideTitleWidget( + axisSide: meta.axisSide, child: Icon(iconMap[itemList[value.toInt()]])); + }), + ), + topTitles: const AxisTitles(), + rightTitles: const AxisTitles(), + ), + gridData: FlGridData( + show: true, + drawVerticalLine: false, + checkToShowHorizontalLine: (value) { + return value.toInt() == value; + }, + getDrawingHorizontalLine: (value) { + return FlLine( + color: colorScheme.onSurface.withOpacity(0.2), + strokeWidth: 1, + ); + }, + ), + barGroups: List.generate( + itemList.length, + (index) => BarChartGroupData(x: index, barRods: [ + BarChartRodData( + fromY: 0, toY: countMap[itemList[index]]!.toDouble(), color: colorScheme.primary) + ]), + ), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (state.finished) ...[ + Text( + '暂无数据', + style: textStyle.titleLarge!.copyWith(color: colorScheme.onSurface), + ), + ] else ...[ + const CircularProgressIndicator(), + ], + ], + ), + ) + ], + ), + ); + } + + Widget buildMoodWrap(String title, List itemList) { + return Container( + padding: const EdgeInsets.all(20.0), + margin: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + decoration: + BoxDecoration(color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(10.0)), + child: Column( + children: [ + Text( + title, + style: textStyle.titleMedium!.copyWith(color: colorScheme.onSurface), + ), + const SizedBox( + height: 10, + ), + AspectRatio( + aspectRatio: 1.0, + child: itemList.isNotEmpty + ? SingleChildScrollView( + child: Wrap( + spacing: 10.0, + runSpacing: 10.0, + children: List.generate(state.moodList.length, (index) { + return MoodIconComponent(value: state.moodList[index]); + }), + ), + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (state.finished) ...[ + Text( + '暂无数据', + style: textStyle.titleLarge!.copyWith(color: colorScheme.onSurface), + ), + ] else ...[ + const CircularProgressIndicator(), + ], + ], + ), + ), + ], + ), + ); + } + + return GetBuilder( + assignId: true, + init: logic, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text( + '分析', + ), + ), + body: ListView( + children: [ + Container( + padding: const EdgeInsets.all(10.0), + margin: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(10.0)), + child: Row( + children: [ + IconButton.filled( + onPressed: () { + logic.openDatePicker(context); + }, + icon: const Icon(Icons.date_range)), + const SizedBox( + width: 10, + ), + Expanded( + child: Text( + '${state.dateRange[0].year}年${state.dateRange[0].month}月${state.dateRange[0].day}日 至 ${state.dateRange[1].year}年${state.dateRange[1].month}月${state.dateRange[1].day}日', + ), + ), + ], + ), + ), + Container( + padding: const EdgeInsets.all(10.0), + margin: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(10.0)), + child: Column( + children: [ + TextButton( + onPressed: () { + logic.getAi(); + }, + child: const Text('AI 分析')), + if (state.reply != '') ...[Text(state.reply)] + ], + ), + ), + buildBarChart('天气', WeatherIcon.map, state.weatherMap, state.weatherList), + buildMoodWrap('心情', state.moodList), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/assistant/assistant_logic.dart b/lib/pages/assistant/assistant_logic.dart new file mode 100644 index 0000000..e6b6bdc --- /dev/null +++ b/lib/pages/assistant/assistant_logic.dart @@ -0,0 +1,163 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/api/api.dart'; +import 'package:mood_diary/common/models/hunyuan.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'assistant_state.dart'; + +class AssistantLogic extends GetxController { + final AssistantState state = AssistantState(); + + //输入框控制器 + late TextEditingController textEditingController = TextEditingController(); + + //ListView控制器 + late ScrollController scrollController = ScrollController(); + + //聚焦对象 + late FocusNode focusNode = FocusNode(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + textEditingController.dispose(); + scrollController.dispose(); + focusNode.dispose(); + super.onClose(); + } + + void handleBack() { + if (focusNode.hasFocus) { + unFocus(); + Future.delayed(const Duration(seconds: 1), () { + Get.backLegacy(); + }); + } else { + Get.backLegacy(); + } + } + + void unFocus() { + focusNode.unfocus(); + } + + void newChat() { + state.messages = []; + update(); + } + + void clearText() { + textEditingController.clear(); + } + + //对话 + Future getAi(String ask) async { + var check = Utils().signatureUtil.checkTencent(); + if (check != null) { + //清空输入框 + clearText(); + //失去焦点 + unFocus(); + //拿到用户提问后,对话上下文中增加一项用户提问 + state.messages.add(Message('user', ask)); + update(); + //带着上下文请求 + var stream = await Api().getHunYuan(check['id']!, check['key']!, state.messages, state.modelVersion); + //如果收到了请求,添加一个回答上下文 + state.messages.add(Message('assistant', '')); + update(); + //接收stream + stream?.listen((content) { + if (content != '' && content.contains('data')) { + HunyuanResponse result = HunyuanResponse.fromJson(jsonDecode(content.split('data: ')[1])); + state.messages.last.content += result.choices!.first.delta!.content!; + HapticFeedback.vibrate(); + update(); + } + }).onDone(() { + toBottom(); + }); + } + } + + void toBottom() { + scrollController.jumpTo(scrollController.position.maxScrollExtent); + } + + String getText() { + return textEditingController.text; + } + + Future checkGetAi() async { + var text = getText(); + if (text != '') { + await getAi(text); + } else { + Utils().noticeUtil.showToast('还没有输入问题'); + } + } + + Future changeModel() async { + await showDialog( + context: Get.context!, + builder: (context) { + return SimpleDialog( + title: const Text('选择模型'), + children: [ + SimpleDialogOption( + child: Row( + children: [ + const Text('hunyuan-lite'), + if (state.modelVersion == 0) ...[const Icon(Icons.check)] + ], + ), + onPressed: () { + state.modelVersion = 0; + state.messages = []; + update(); + Get.backLegacy(); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + const Text('hunyuan-standard'), + if (state.modelVersion == 1) ...[const Icon(Icons.check)] + ], + ), + onPressed: () { + state.modelVersion = 1; + state.messages = []; + update(); + Get.backLegacy(); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + const Text('hunyuan-pro'), + if (state.modelVersion == 2) ...[const Icon(Icons.check)] + ], + ), + onPressed: () { + state.modelVersion = 2; + state.messages = []; + update(); + Get.backLegacy(); + }, + ) + ], + ); + }); + } +} diff --git a/lib/pages/assistant/assistant_state.dart b/lib/pages/assistant/assistant_state.dart new file mode 100644 index 0000000..a7be902 --- /dev/null +++ b/lib/pages/assistant/assistant_state.dart @@ -0,0 +1,16 @@ +import 'package:mood_diary/common/models/hunyuan.dart'; + +class AssistantState { + //对话上下文 + late List messages; + + //模型版本 + late int modelVersion; + + AssistantState() { + messages = []; + modelVersion = 0; + + ///Initialize variables + } +} diff --git a/lib/pages/assistant/assistant_view.dart b/lib/pages/assistant/assistant_view.dart new file mode 100644 index 0000000..fd2dd71 --- /dev/null +++ b/lib/pages/assistant/assistant_view.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:markdown_widget/markdown_widget.dart'; + +import 'assistant_logic.dart'; + +class AssistantPage extends StatelessWidget { + const AssistantPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final modelName = ['hunyuan-lite', 'hunyuan-standard', 'hunyuan-pro']; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return GestureDetector( + onTap: () { + logic.unFocus(); + }, + child: Scaffold( + appBar: AppBar( + title: const Text('AI 助手'), + leading: IconButton( + onPressed: () { + logic.handleBack(); + }, + icon: const Icon(Icons.arrow_back_outlined), + ), + actions: [ + IconButton( + onPressed: () { + logic.newChat(); + }, + icon: const Icon(Icons.refresh)), + ], + ), + body: Column( + children: [ + Flexible( + child: state.messages.isEmpty + ? const Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('暂无对话'), + ], + ) + : ListView.builder( + controller: logic.scrollController, + itemBuilder: (context, index) { + if (state.messages[index].role == 'user') { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Text( + state.messages[index].content, + style: TextStyle(color: colorScheme.primary), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: colorScheme.surfaceContainer, + borderRadius: const BorderRadius.all(Radius.circular(10.0))), + child: MarkdownBlock( + data: state.messages[index].content, + selectable: true, + config: colorScheme.brightness == Brightness.dark + ? MarkdownConfig.darkConfig + : MarkdownConfig.defaultConfig, + ), + ), + ); + } + }, + itemCount: state.messages.length, + )), + Container( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + Row( + children: [ + Text('当前模型:${modelName[state.modelVersion]}'), + IconButton( + onPressed: () { + logic.changeModel(); + }, + icon: const Icon(Icons.change_circle_outlined)) + ], + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: TextField( + focusNode: logic.focusNode, + controller: logic.textEditingController, + minLines: 1, + maxLines: 10, + decoration: InputDecoration( + fillColor: colorScheme.surfaceContainerHighest, + filled: true, + suffixIcon: IconButton( + onPressed: () { + logic.clearText(); + }, + icon: const Icon(Icons.cancel), + ), + hintText: '消息', + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + ), + )), + IconButton.filled( + onPressed: () { + logic.checkGetAi(); + }, + icon: const Icon(Icons.arrow_upward)) + ], + ), + ], + ), + ) + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/category_manager/category_manager_logic.dart b/lib/pages/category_manager/category_manager_logic.dart new file mode 100644 index 0000000..c3ecd62 --- /dev/null +++ b/lib/pages/category_manager/category_manager_logic.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'category_manager_state.dart'; + +class CategoryManagerLogic extends GetxController { + final CategoryManagerState state = CategoryManagerState(); + + late TextEditingController textEditingController = TextEditingController(); + + @override + void onInit() { + // TODO: implement onInit + getCategory(); + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + textEditingController.dispose(); + super.onClose(); + } + + Future getCategory() async { + state.categoryList.value = Utils().isarUtil.getAllCategory(); + } + + Future addCategory() async { + if (textEditingController.text.isNotEmpty) { + if (await Utils().isarUtil.insertACategory(Category()..categoryName = textEditingController.text)) { + Get.backLegacy(); + await getCategory(); + } else { + textEditingController.clear(); + Utils().noticeUtil.showToast('分类已存在'); + } + } + } + + Future editCategory(String categoryId) async { + if (textEditingController.text.isNotEmpty) { + await Utils().isarUtil.updateACategory(Category() + ..id = categoryId + ..categoryName = textEditingController.text); + Get.backLegacy(); + await getCategory(); + } + } + + Future deleteCategory(String id) async { + if (await Utils().isarUtil.deleteACategory(id)) { + Utils().noticeUtil.showToast('删除成功'); + await getCategory(); + } else { + Utils().noticeUtil.showToast('删除失败,当前分类下还有日记'); + } + } + + void clearInput() { + textEditingController.clear(); + } + + void editInput(String value) { + textEditingController.text = value; + } +} diff --git a/lib/pages/category_manager/category_manager_state.dart b/lib/pages/category_manager/category_manager_state.dart new file mode 100644 index 0000000..f0ccbae --- /dev/null +++ b/lib/pages/category_manager/category_manager_state.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; + +class CategoryManagerState { + late RxList categoryList; + + CategoryManagerState() { + categoryList = [].obs; + + ///Initialize variables + } +} diff --git a/lib/pages/category_manager/category_manager_view.dart b/lib/pages/category_manager/category_manager_view.dart new file mode 100644 index 0000000..506c9d2 --- /dev/null +++ b/lib/pages/category_manager/category_manager_view.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; + +import 'category_manager_logic.dart'; + +class CategoryManagerPage extends StatelessWidget { + const CategoryManagerPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final i18n = AppLocalizations.of(context)!; + + Widget inputDialog(Function() operate) { + return AlertDialog( + title: TextField( + maxLines: 1, + controller: logic.textEditingController, + decoration: InputDecoration( + fillColor: colorScheme.secondaryContainer, + border: const UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide.none, + ), + filled: true, + labelText: '分类', + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + TextButton(onPressed: operate, child: Text(i18n.ok)) + ], + ); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text('分类管理'), + ), + body: Obx(() { + return ListView.builder( + itemBuilder: (context, index) { + return ListTile( + title: Text(state.categoryList.value[index].categoryName), + onTap: null, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + IconButton( + onPressed: () { + logic.editInput(state.categoryList.value[index].categoryName); + showDialog( + context: context, + builder: (context) { + return inputDialog(() { + logic.editCategory(state.categoryList.value[index].id); + }); + }); + }, + icon: const Icon(Icons.edit), + style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + ), + const Text('编辑'), + ], + ), + Column( + children: [ + IconButton( + onPressed: () { + logic.deleteCategory(state.categoryList.value[index].id); + }, + icon: const Icon(Icons.delete_forever), + style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + color: colorScheme.error, + ), + Text( + '删除', + style: TextStyle(color: colorScheme.error), + ), + ], + ) + ], + ), + ); + }, + itemCount: state.categoryList.value.length, + ); + }), + floatingActionButton: FloatingActionButton.extended( + onPressed: () async { + logic.clearInput(); + showDialog( + context: context, + builder: (context) { + return inputDialog(() { + logic.addCategory(); + }); + }); + }, + icon: const Icon(Icons.add), + label: const Text('添加分类'), + ), + ); + }, + ); + } +} diff --git a/lib/pages/diary/diary_logic.dart b/lib/pages/diary/diary_logic.dart new file mode 100644 index 0000000..71ee690 --- /dev/null +++ b/lib/pages/diary/diary_logic.dart @@ -0,0 +1,102 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'diary_state.dart'; + +class DiaryLogic extends GetxController { + final DiaryState state = DiaryState(); + + //编辑器控制器 + late QuillController quillController = QuillController( + document: Document.fromJson(jsonDecode(state.diary.content)), + readOnly: true, + selection: const TextSelection.collapsed(offset: 0), + ); + + late HomeLogic homeLogic = Bind.find(); + + @override + void onInit() { + // TODO: implement onInit + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + quillController.dispose(); + super.onClose(); + } + + void toLeft() {} + + //减小字号 + TextStyle lowText(TextStyle currentStyle, TextTheme textTheme) { + if (currentStyle == textTheme.bodyLarge) { + return textTheme.bodyMedium!; + } + if (currentStyle == textTheme.bodyMedium) { + return textTheme.bodySmall!; + } + return currentStyle; + } + + //增大字号 + TextStyle upText(TextStyle currentStyle, TextTheme textTheme) { + if (currentStyle == textTheme.bodySmall) { + return textTheme.bodyMedium!; + } + if (currentStyle == textTheme.bodyMedium) { + return textTheme.bodyLarge!; + } + return currentStyle; + } + + //点击图片跳转到图片预览页面 + void toPhotoView(List imagePathList, int index) { + Get.toNamed(AppRoutes.photoPage, arguments: [imagePathList, index]); + } + + //点击分享跳转到分享页面 + Future toSharePage() async { + Get.toNamed(AppRoutes.sharePage, arguments: state.diary); + } + + //编辑日记 + Future toEditPage(Diary diary) async { + //这里参数为diary,表示编辑日记,等待跳转结果为changed,重新获取日记 + if ((await Get.toNamed(AppRoutes.editPage, arguments: diary)) == 'changed') { + //重新获取日记 + state.diary = (await Utils().isarUtil.getDiaryByID(state.diary.id))!; + quillController = QuillController( + document: Document.fromJson(jsonDecode(state.diary.content)), + readOnly: true, + selection: const TextSelection.collapsed(offset: 0), + ); + await homeLogic.updateDiary(); + update(); + } + } + + //放入回收站 + Future delete(Diary diary) async { + Get.backLegacy(result: 'delete'); + await homeLogic.updateDiary(); + await Utils().isarUtil.updateADiary(diary..show = false); + } +} diff --git a/lib/pages/diary/diary_state.dart b/lib/pages/diary/diary_state.dart new file mode 100644 index 0000000..161ee67 --- /dev/null +++ b/lib/pages/diary/diary_state.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; + +class DiaryState { + late Diary diary; + + DiaryState() { + diary = Get.arguments; + + ///Initialize variables + } +} diff --git a/lib/pages/diary/diary_view.dart b/lib/pages/diary/diary_view.dart new file mode 100644 index 0000000..f84a4e3 --- /dev/null +++ b/lib/pages/diary/diary_view.dart @@ -0,0 +1,252 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; +import 'package:mood_diary/common/values/icons.dart'; +import 'package:mood_diary/components/audio_player/audio_player_view.dart'; +import 'package:mood_diary/components/mood_icon/mood_icon_view.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'diary_logic.dart'; + +class DiaryPage extends StatelessWidget { + const DiaryPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final size = MediaQuery.sizeOf(context); + final viewPadding = MediaQuery.viewPaddingOf(context); + var imageColor = state.diary.imageColor; + final colorScheme = imageColor != null + ? ColorScheme.fromSeed( + seedColor: Color(imageColor), + brightness: Theme.of(context).brightness, + ) + : Theme.of(context).colorScheme; + Widget buildChipList() { + return Wrap( + spacing: 8.0, + children: [ + Chip( + label: MoodIconComponent(value: state.diary.mood), + backgroundColor: colorScheme.secondaryContainer, + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + labelPadding: EdgeInsets.zero, + ), + Chip( + label: Text( + DateFormat.yMMMd().add_Hms().format(state.diary.time), + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + avatar: const Icon(Icons.access_time_outlined), + iconTheme: IconThemeData(color: colorScheme.secondary), + backgroundColor: colorScheme.secondaryContainer, + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only(right: 8), + ), + if (state.diary.weather.isNotEmpty) ...[ + Chip( + label: Text( + '${state.diary.weather[2]} ${state.diary.weather[1]}°C', + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + avatar: Icon( + WeatherIcon.map[state.diary.weather[0]], + color: colorScheme.primary, + ), + iconTheme: IconThemeData(color: colorScheme.secondary), + backgroundColor: colorScheme.secondaryContainer, + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only(right: 8), + ) + ], + Chip( + label: Text( + '${state.diary.contentText.length} 字', + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + avatar: Icon( + Icons.text_fields_outlined, + color: colorScheme.primary, + ), + iconTheme: IconThemeData(color: colorScheme.secondary), + backgroundColor: colorScheme.secondaryContainer, + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only(right: 8), + ), + ...List.generate(state.diary.tags.length, (index) { + return Chip( + label: Text( + state.diary.tags[index], + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + avatar: Icon( + Icons.tag_outlined, + color: colorScheme.primary, + ), + iconTheme: IconThemeData(color: colorScheme.secondary), + backgroundColor: colorScheme.secondaryContainer, + side: BorderSide.none, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: EdgeInsets.zero, + labelPadding: const EdgeInsets.only(right: 8), + ); + }) + ], + ); + } + + List buildMultiImages() { + return List.generate(state.diary.imageName.length - 1, (index) { + var actualIndex = index + 1; // 从第二个元素开始 + var imageProvider = FileImage(File(Utils().fileUtil.getRealPath('image', state.diary.imageName[actualIndex]))); + return InkWell( + onTap: () { + logic.toPhotoView(state.diary.imageName, actualIndex); + }, + child: Container( + width: ((size.width - 32) / 3).truncateToDouble(), + height: ((size.width - 32) / 3).truncateToDouble(), + constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + border: Border.all(color: colorScheme.outline.withOpacity(0.8)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + ); + }); + } + + Widget buildAudioList() { + return Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...List.generate(state.diary.audioName.length, (index) { + return AudioPlayerComponent( + path: Utils().fileUtil.getRealPath('audio', state.diary.audioName[index]), index: index.toString()); + }) + ], + ); + } + + return GetBuilder( + assignId: true, + init: logic, + builder: (logic) { + var imageColor = state.diary.imageColor; + return Theme( + data: imageColor != null + ? Theme.of(context).copyWith( + colorScheme: ColorScheme.fromSeed( + seedColor: Color(imageColor), + brightness: Theme.of(context).brightness, + ), + ) + : Theme.of(context), + child: Scaffold( + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: state.diary.aspect != null + ? min(size.width / state.diary.aspect!, size.height - viewPadding.top) + : null, + flexibleSpace: FlexibleSpaceBar( + collapseMode: CollapseMode.pin, + background: state.diary.imageName.isNotEmpty + ? InkWell( + onTap: () { + logic.toPhotoView(state.diary.imageName, 0); + }, + child: Image.file( + File(Utils().fileUtil.getRealPath('image', state.diary.imageName.first)), + fit: BoxFit.cover, + ), + ) + : null, + ), + pinned: true, + actions: [ + IconButton( + onPressed: () { + logic.delete(state.diary); + }, + icon: const Icon(Icons.delete)), + IconButton( + onPressed: () { + logic.toEditPage(state.diary); + }, + icon: const Icon(Icons.edit)), + IconButton( + onPressed: () { + logic.toSharePage(); + }, + icon: const Icon(Icons.share), + ), + ], + ), + SliverList.list(children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, padding: const EdgeInsets.all(8.0), child: buildChipList()), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Container( + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerLow, + borderRadius: const BorderRadius.all(Radius.circular(8.0))), + child: QuillEditor.basic( + controller: logic.quillController, + configurations: const QuillEditorConfigurations( + showCursor: false, + sharedConfigurations: QuillSharedConfigurations(), + ), + ), + ), + ), + const SizedBox(height: 8.0), + //绘制图片 + if (state.diary.imageName.length > 1) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: buildMultiImages(), + ), + ), + const SizedBox(height: 8.0), + ], + if (state.diary.audioName.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: buildAudioList(), + ), + const SizedBox(height: 8.0), + ] + ]) + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/draw/draw_logic.dart b/lib/pages/draw/draw_logic.dart new file mode 100644 index 0000000..a2dd470 --- /dev/null +++ b/lib/pages/draw/draw_logic.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:flutter_drawing_board/flutter_drawing_board.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/edit/edit_logic.dart'; + +import 'draw_state.dart'; + +class DrawLogic extends GetxController { + final DrawState state = DrawState(); + late DrawingController drawingController = DrawingController()..setStyle(color: state.pickerColor); + late final editLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + drawingController.dispose(); + super.onClose(); + } + + Future getImageData() async { + var data = await drawingController.getImageData(); + var image = data!.buffer.asUint8List(); + editLogic.pickDraw(image); + Get.backLegacy(); + } + + Future showColorPicker() async { + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('选择颜色'), + content: SingleChildScrollView( + child: ColorPicker( + pickerColor: state.pickerColor, + onColorChanged: (Color value) { + state.pickerColor = value; + update(); + }, + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: const Text('取消')), + TextButton( + onPressed: () { + Get.backLegacy(); + setColor(); + }, + child: const Text('确认')) + ], + ); + }); + } + + void setColor() { + drawingController.setStyle(color: state.pickerColor); + update(); + } + + Future showCheck() async { + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认保存吗'), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: const Text('取消')), + TextButton( + onPressed: () async { + Get.backLegacy(); + await getImageData(); + }, + child: const Text('确认')) + ], + ); + }); + } +} diff --git a/lib/pages/draw/draw_state.dart b/lib/pages/draw/draw_state.dart new file mode 100644 index 0000000..36444e4 --- /dev/null +++ b/lib/pages/draw/draw_state.dart @@ -0,0 +1,9 @@ +import 'dart:ui'; + +class DrawState { + Color pickerColor = const Color(0xFF000000); + + DrawState() { + ///Initialize variables + } +} diff --git a/lib/pages/draw/draw_view.dart b/lib/pages/draw/draw_view.dart new file mode 100644 index 0000000..0a8cef5 --- /dev/null +++ b/lib/pages/draw/draw_view.dart @@ -0,0 +1,76 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_drawing_board/flutter_drawing_board.dart'; +import 'package:get/get.dart'; + +import 'draw_logic.dart'; + +class DrawPage extends StatelessWidget { + const DrawPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final size = MediaQuery.sizeOf(context); + final colorScheme = Theme.of(context).colorScheme; + var drawWidth = min(300.0, size.width * 0.8); + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text('画板'), + ), + body: Center( + child: Container( + width: drawWidth, + height: (drawWidth * 1.618) + 96, + decoration: BoxDecoration( + border: Border.fromBorderSide( + BorderSide(strokeAlign: BorderSide.strokeAlignOutside, color: colorScheme.primary, width: 4.0))), + child: DrawingBoard( + controller: logic.drawingController, + background: Container( + decoration: const BoxDecoration( + color: Colors.white, + ), + width: drawWidth, + height: drawWidth * 1.618, + ), + boardClipBehavior: Clip.none, + boardConstrained: true, + defaultToolsBuilder: (Type t, _) { + return DrawingBoard.defaultTools(t, logic.drawingController) + ..insert( + 0, + DefToolItem( + icon: Icons.circle, + color: state.pickerColor, + onTap: () { + logic.showColorPicker(); + }, + isActive: false)); + }, + showDefaultActions: true, + showDefaultTools: true, + boardScaleEnabled: false, + boardPanEnabled: false, + ), + ), + ), + persistentFooterButtons: [ + FloatingActionButton( + onPressed: () { + logic.showCheck(); + }, + child: const Icon(Icons.check), + ) + ], + ); + }, + ); + } +} diff --git a/lib/pages/edit/edit_logic.dart b/lib/pages/edit/edit_logic.dart new file mode 100644 index 0000000..4d899e9 --- /dev/null +++ b/lib/pages/edit/edit_logic.dart @@ -0,0 +1,474 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mood_diary/api/api.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/common/values/keyboard_state.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:uuid/uuid.dart'; + +import 'edit_state.dart'; + +class EditLogic extends GetxController with GetSingleTickerProviderStateMixin, WidgetsBindingObserver { + final EditState state = EditState(); + + //分类控制器 + late TextEditingController tagTextEditingController = TextEditingController(); + + //标题 + late TextEditingController titleTextEditingController = TextEditingController(); + + late TabController tabController = TabController(length: 2, vsync: this); + + //编辑器控制器 + late QuillController quillController = QuillController.basic(); + + //聚焦对象 + late FocusNode focusNode = FocusNode(); + List heightList = []; + + @override + void didChangeMetrics() { + WidgetsBinding.instance.addPostFrameCallback((_) { + var height = MediaQuery.viewInsetsOf(Get.context!).bottom; + + if (heightList.isNotEmpty && height != heightList.last) { + if (height > heightList.last && state.keyboardState != KeyboardState.opening) { + state.keyboardState = KeyboardState.opening; + //正在打开 + } else if (height < heightList.last && state.keyboardState != KeyboardState.closing) { + state.keyboardState = KeyboardState.closing; + //正在关闭 + unFocus(); + } + } + + // 只在高度变化时记录高度 + if (heightList.isEmpty || height != heightList.last) { + heightList.add(height); + } + + // 当高度为0且键盘经历了开启关闭过程时,认为键盘已完全关闭 + if (height == 0 && state.keyboardState != KeyboardState.closed) { + state.keyboardState = KeyboardState.closed; + heightList.clear(); + //已经关闭 + } + }); + super.didChangeMetrics(); + } + + @override + void onInit() async { + // TODO: implement onInit + //如果是新增 + WidgetsBinding.instance.addObserver(this); + if (Get.arguments == 'new') { + state.isNew = true; + } else { + //如果是编辑 + state.oldDiary = Get.arguments; + state.content = state.oldDiary!.content; + quillController = QuillController( + document: Document.fromJson(jsonDecode(state.content)), selection: const TextSelection.collapsed(offset: 0)); + state.currentDateTime = state.oldDiary!.time; + state.currentMoodRate = state.oldDiary!.mood.obs; + state.currentWeather = state.oldDiary!.weather; + state.audioNameList = state.oldDiary!.audioName; + state.videoNameList = state.oldDiary!.videoName; + state.tagList = state.oldDiary!.tags; + state.imageNameList = state.oldDiary!.imageName; + state.categoryId = state.oldDiary!.categoryId; + titleTextEditingController = TextEditingController(text: state.oldDiary!.title); + if (state.categoryId != null) { + state.categoryName = (await Utils().isarUtil.getCategoryName(state.categoryId!))!.categoryName; + } + //拷贝图片数据 + for (var name in state.imageNameList) { + state.imageList.add(File(Utils().fileUtil.getRealPath('image', name)).readAsBytesSync()); + } + //拷贝音频数据 + for (var name in state.audioNameList) { + File(Utils().fileUtil.getRealPath('audio', name)).copy(Utils().fileUtil.getCachePath(name)); + } + } + update(); + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + WidgetsBinding.instance.removeObserver(this); + tagTextEditingController.dispose(); + titleTextEditingController.dispose(); + tabController.dispose(); + quillController.dispose(); + super.onClose(); + } + + Future addNewImage(Uint8List data) async { + Utils().noticeUtil.showToast('图片处理中'); + //压缩图片 + var newImage = await Utils().mediaUtil.compressImage(data); + //图片列表中新增一个 + state.imageList.add(newImage); + //名称列表中新增一个,使用 uuid 作为名称 + state.imageNameList.add('image-${const Uuid().v7()}.webp'); + update(); + Utils().noticeUtil.showToast('处理完成'); + } + + //单张照片 + Future pickPhoto(ImageSource imageSource) async { + //获取一张图片 + final XFile? photo = await Utils().mediaUtil.pickPhoto(imageSource); + if (photo != null) { + //关闭dialog + Get.backLegacy(); + await addNewImage(await photo.readAsBytes()); + } else { + //关闭dialog + Get.backLegacy(); + //弹出一个提示 + Utils().noticeUtil.showToast('取消图片选择'); + } + } + + //画图照片 + Future pickDraw(dataList) async { + await addNewImage(dataList); + } + + //网络图片 + Future networkImage() async { + //关闭dialog + Get.backLegacy(); + Utils().noticeUtil.showToast('图片获取中'); + var imageUrl = await Api().updateImageUrl(); + if (imageUrl != null) { + var imageData = await Api().getImageData(imageUrl.first); + addNewImage(imageData!); + } + } + + //相册选择多张照片 + Future pickMultiPhoto() async { + //获取一堆照片,最多10张 + List photoList = await Utils().mediaUtil.pickMultiPhoto(10); + if (photoList.isNotEmpty) { + //关闭dialog + Get.backLegacy(); + if (photoList.length > 10 - state.imageList.length) { + photoList = photoList.sublist(0, 10 - state.imageList.length); + } + for (var photo in photoList) { + addNewImage(await photo.readAsBytes()); + } + } else { + //关闭dialog + Get.backLegacy(); + //弹出一个提示 + Utils().noticeUtil.showToast('取消图片选择'); + update(); + } + } + + //删除图片 + void deleteImage(index) { + //如果是封面,还要删掉封面 + if (state.coverImageName == state.imageNameList[index]) { + state.coverImage = null; + state.coverImageName = null; + } + state.imageList.removeAt(index); + var fileName = state.imageNameList.removeAt(index); + //如果不是新增,还要删除源文件 + if (state.isNew == false) { + Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('image', fileName)); + } + Get.backLegacy(); + Utils().noticeUtil.showToast('删除成功'); + update(); + } + + //长按设置封面 + void setCover(int index) { + state.coverImage = state.imageList[index]; + state.coverImageName = state.imageNameList[index]; + Utils().noticeUtil.showToast('设置第${index + 1}张图片为封面'); + update(); + } + + void checkCover() { + //如果没有手动指定封面,默认第一张 + + if (state.coverImage == null) { + //如果图片都没有,从默认封面获取颜色 + if (state.imageList.isEmpty) { + return; + } else { + state.coverImage = state.imageList.first; + state.coverImageName = state.imageNameList.first; + } + } else { + //如果不为空,说明手动指定了,那么替换位置到首部 + state.imageList + ..remove(state.coverImage) + ..insert(0, state.coverImage!); + state.imageNameList + ..remove(state.coverImageName) + ..insert(0, state.coverImageName!); + } + } + + //获取封面颜色 + Future getCoverColor() async { + if (state.imageList.isNotEmpty) { + return (await Utils().mediaUtil.getColorScheme(MemoryImage(state.imageList.first))).value; + } else { + return null; + } + } + + //获取封面比例 + Future getCoverAspect() async { + //如果有封面就获取 + if (state.imageList.isNotEmpty) { + return await Utils().mediaUtil.getImageAspectRatio(MemoryImage(state.imageList.first)); + } else { + return null; + } + } + + //保存日记 + Future saveDiary() async { + if (checkIsNotEmpty()) { + //检查封面 + checkCover(); + var diary = Diary( + id: '', + categoryId: state.categoryId, + title: state.title, + content: state.content, + contentText: quillController.document.toPlainText().trim(), + time: state.currentDateTime, + show: true, + mood: state.currentMoodRate.value, + weather: [], + imageName: state.imageNameList, + audioName: state.audioNameList, + videoName: state.videoNameList, + tags: state.tagList, + imageColor: await getCoverColor(), + aspect: await getCoverAspect(), + ); + //先把日记插入到数据库中 + await Utils().isarUtil.insertADiary(diary); + //保存图片 + await Utils().mediaUtil.saveImages(Map.fromIterables(state.imageNameList, state.imageList)); + //保存录音 + await Utils().mediaUtil.savaAudio(state.audioNameList); + Get.backLegacy(); + Utils().noticeUtil.showToast('保存成功'); + } else { + Utils().noticeUtil.showToast('还有东西没有填写'); + } + } + + //更新日记 + Future updateDiary() async { + if (checkIsNotEmpty()) { + //检查封面 + checkCover(); + var diary = Diary( + id: state.oldDiary!.id, + categoryId: state.categoryId, + title: state.title, + content: state.content, + contentText: quillController.document.toPlainText(), + time: state.currentDateTime, + show: true, + mood: state.currentMoodRate.value, + weather: [], + imageName: state.imageNameList, + audioName: state.audioNameList, + videoName: state.videoNameList, + tags: state.tagList, + imageColor: await getCoverColor(), + aspect: await getCoverAspect(), + ); + //更新数据库中的日记 + await Utils().isarUtil.updateADiary(diary); + //保存图片 + await Utils().mediaUtil.saveImages(Map.fromIterables(state.imageNameList, state.imageList)); + //保存录音 + await Utils().mediaUtil.savaAudio(state.audioNameList); + Get.backLegacy(result: 'changed'); + Utils().noticeUtil.showToast('修改成功'); + } + } + + void handleBack() { + DateTime currentTime = DateTime.now(); + if (state.oldTime != null && currentTime.difference(state.oldTime!) < const Duration(seconds: 3)) { + Get.backLegacy(); + } else { + state.oldTime = currentTime; + Utils().noticeUtil.showToast('再滑一次退出'); + } + } + + //日期选择器 + void selectedDate(DateTime now) { + //如果选择的是当天,变回去 + if (state.oldDateTime.year == now.year && + state.oldDateTime.month == now.month && + state.oldDateTime.day == now.day) { + state.currentDateTime = state.oldDateTime; + } else { + state.currentDateTime = state.currentDateTime.copyWith(year: now.year, month: now.month, day: now.day); + } + update(); + } + + Future changeDate() async { + var nowDateTime = await showDatePicker( + context: Get.context!, + initialDate: state.currentDateTime, + lastDate: state.oldDateTime, + initialDatePickerMode: DatePickerMode.day, + initialEntryMode: DatePickerEntryMode.calendarOnly, + firstDate: state.oldDateTime.subtract(const Duration(days: 31)), + ); + if (nowDateTime != null) { + selectedDate(nowDateTime); + } + } + + //时间选择器 + void selectedTime(TimeOfDay now) { + state.currentDateTime = state.currentDateTime.copyWith(hour: now.hour, minute: now.minute); + update(); + } + + Future changeTime() async { + var nowTime = + await showTimePicker(context: Get.context!, initialTime: TimeOfDay.fromDateTime(state.currentDateTime)); + if (nowTime != null) { + selectedTime(nowTime); + } + } + + //监听文本输入 + void updateContent(String value) { + state.content = value; + update(); + } + + bool checkIsNotEmpty() { + if (quillController.document.isEmpty()) { + return false; + } else { + //内容序列化为json + state.content = jsonEncode(quillController.document.toDelta().toJson()); + //如果有title保存到数据库 + if (titleTextEditingController.text.isNotEmpty) { + state.title = titleTextEditingController.text; + } + return true; + } + } + + //点击其他地方失去焦点 + void unFocus() { + focusNode.unfocus(); + } + + //去画画 + void toDrawPage() { + Get.backLegacy(); + Get.toNamed(AppRoutes.drawPage); + } + + void changeRate(value) { + state.currentMoodRate.value = value; + } + + void changeColor(value) {} + + //获取天气 + Future getWeather() async { + state.isProcessing = true; + update(); + var res = await Api().updateWeather(); + if (res != null) { + state.currentWeather = res; + state.isProcessing = false; + Utils().noticeUtil.showToast('获取成功'); + update(); + } + } + + //获取音频名称 + void setAudioName(String name) { + state.audioNameList.add(name); + update(); + } + + //删除音频 + Future deleteAudio(int index) async { + await Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('audio', state.audioNameList[index])); + state.audioNameList.removeAt(index); + update(); + Utils().noticeUtil.showToast('删除成功'); + } + + void cancelAddTag() { + Get.backLegacy(); + tagTextEditingController.clear(); + } + + //添加一个标签 + void addTag() { + Get.backLegacy(); + if (tagTextEditingController.text.isNotEmpty) { + state.tagList.add(tagTextEditingController.text); + tagTextEditingController.clear(); + update(); + } + } + + //移除一个标签 + void removeTag(index) { + state.tagList.removeAt(index); + update(); + } + + void selectCategory(String id) async { + state.categoryId = id; + var category = await Utils().isarUtil.getCategoryName(id); + if (category != null) { + state.categoryName = category.categoryName; + } + update(); + } + + void selectTabView(index) { + state.tabIndex.value = index; + tabController.animateTo(index, duration: const Duration(milliseconds: 100), curve: Curves.easeInOut); + } +} diff --git a/lib/pages/edit/edit_state.dart b/lib/pages/edit/edit_state.dart new file mode 100644 index 0000000..c369fe7 --- /dev/null +++ b/lib/pages/edit/edit_state.dart @@ -0,0 +1,86 @@ +import 'dart:typed_data'; + +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/common/values/keyboard_state.dart'; + +class EditState { + //当前天气 + late List currentWeather; + + //封面图片 + late Uint8List? coverImage; + + //封面图片名称 + late String? coverImageName; + + //其他图片 + late List imageList; + + //其他图片名称 + late List imageNameList; + + //标题 + String? title; + + //内容 + late String content; + + //日记的日期 + late DateTime currentDateTime; + + //编辑还是新增 + late bool isNew; + + //编辑的原始对象 + late Diary? oldDiary; + + late RxInt tabIndex; + + //拷贝一份第一次打开时的时间 + late DateTime oldDateTime; + late DateTime? oldTime; + + late RxDouble currentMoodRate; + late bool isProcessing; + + late List availableCategoryList; + + late List audioNameList; + late List videoNameList; + late List tagList; + + String? categoryId; + + late String categoryName; + late KeyboardState keyboardState; + + EditState() { + oldTime = null; + coverImage = null; + categoryId = null; + tabIndex = 0.obs; + categoryName = ''; + //默认为中性心情 + currentMoodRate = 0.5.obs; + content = ''; + isProcessing = false; + coverImageName = null; + imageList = []; + imageNameList = []; + audioNameList = []; + videoNameList = []; + tagList = []; + currentWeather = []; + oldDiary = null; + + isNew = false; + //默认值为当天 + currentDateTime = DateTime.now(); + keyboardState = KeyboardState.closed; + oldDateTime = currentDateTime; + + ///Initialize variables + } +} diff --git a/lib/pages/edit/edit_view.dart b/lib/pages/edit/edit_view.dart new file mode 100644 index 0000000..04ecedf --- /dev/null +++ b/lib/pages/edit/edit_view.dart @@ -0,0 +1,490 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mood_diary/components/audio_player/audio_player_view.dart'; +import 'package:mood_diary/components/category_add/category_add_view.dart'; +import 'package:mood_diary/components/mood_icon/mood_icon_view.dart'; +import 'package:mood_diary/components/record_sheet/record_sheet_view.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'edit_logic.dart'; + +class EditPage extends StatelessWidget { + const EditPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final size = MediaQuery.sizeOf(context); + final i18n = AppLocalizations.of(context)!; + + //标签列表 + Widget? buildTagList() { + return state.tagList.isNotEmpty + ? Wrap( + spacing: 8.0, + children: List.generate(state.tagList.length, (index) { + return Chip( + label: Text( + state.tagList[index], + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + backgroundColor: colorScheme.secondaryContainer, + onDeleted: () { + logic.removeTag(index); + }, + ); + }), + ) + : null; + } + + Widget buildAudioPlayer() { + return Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: [ + ...List.generate(state.audioNameList.length, (index) { + return AudioPlayerComponent( + path: Utils().fileUtil.getCachePath(state.audioNameList[index]), + index: index.toString(), + isEdit: true, + ); + }), + ActionChip( + label: const Text('添加'), + avatar: const Icon(Icons.add), + onPressed: () { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + builder: (context) { + return const RecordSheetComponent(); + }); + }, + ) + ], + ); + } + + Widget buildImage() { + return Wrap( + spacing: 8.0, + children: [ + ...List.generate(state.imageList.length, (index) { + return InkWell( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + onLongPress: () { + logic.setCover(index); + }, + child: Container( + constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150), + width: ((size.width - 56.0) / 3).truncateToDouble(), + height: ((size.width - 56.0) / 3).truncateToDouble(), + padding: const EdgeInsets.all(2.0), + margin: const EdgeInsets.symmetric(vertical: 4.0), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + border: Border.all(color: colorScheme.outline.withOpacity(0.5)), + image: DecorationImage( + image: MemoryImage(state.imageList[index]), + fit: BoxFit.cover, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: colorScheme.surface.withOpacity(0.5), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('确认删除这张照片吗'), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: const Text('取消')), + TextButton( + onPressed: () { + logic.deleteImage(index); + }, + child: const Text('确认')) + ], + ); + }); + }, + constraints: const BoxConstraints(), + icon: Icon( + Icons.remove_circle_outlined, + color: colorScheme.tertiary, + ), + style: IconButton.styleFrom( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + padding: const EdgeInsets.all(4.0), + ), + ), + if (state.coverImageName == state.imageNameList[index]) ...[ + Padding( + padding: const EdgeInsets.all(4.0), + child: Icon( + Icons.stars, + color: colorScheme.primary, + ), + ) + ] + ], + ), + ), + ], + ), + ), + ); + }), + if (state.imageList.length < 10) ...[ + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + onTap: () { + showDialog( + context: context, + builder: (context) { + return SimpleDialog( + title: const Text('选择来源'), + children: [ + SimpleDialogOption( + child: const Row( + children: [ + Icon(Icons.photo_library_outlined), + SizedBox( + width: 10, + ), + Text('相册'), + ], + ), + onPressed: () { + logic.pickMultiPhoto(); + }, + ), + SimpleDialogOption( + child: const Row( + children: [ + Icon(Icons.camera_alt_outlined), + SizedBox( + width: 10, + ), + Text('相机'), + ], + ), + onPressed: () { + logic.pickPhoto(ImageSource.camera); + }, + ), + SimpleDialogOption( + child: const Row( + children: [ + Icon(Icons.image_search_outlined), + SizedBox( + width: 10, + ), + Text('网络'), + ], + ), + onPressed: () { + logic.networkImage(); + }, + ), + SimpleDialogOption( + child: const Row( + children: [ + Icon(Icons.draw_outlined), + SizedBox( + width: 10, + ), + Text('画画'), + ], + ), + onPressed: () { + logic.toDrawPage(); + }, + ), + ], + ); + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + color: colorScheme.surfaceContainerHighest, + ), + margin: const EdgeInsets.symmetric(vertical: 4.0), + constraints: const BoxConstraints(maxWidth: 150, maxHeight: 150), + width: ((size.width - 56.0) / 3).truncateToDouble(), + height: ((size.width - 56.0) / 3).truncateToDouble(), + child: const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add), + ], + ), + ), + ) + ], + ], + ); + } + + Widget buildDetail() { + return ListView( + children: [ + ListTile( + onTap: null, + title: const Text('日期与时间'), + subtitle: Text(state.currentDateTime.toString().split('.')[0]), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton.filledTonal( + onPressed: () async { + await logic.changeDate(); + }, + icon: const Icon(Icons.date_range), + ), + IconButton.filledTonal( + onPressed: () async { + await logic.changeTime(); + }, + icon: const Icon(Icons.access_time), + ) + ], + ), + ), + ListTile( + title: const Text('天气'), + subtitle: state.currentWeather.isNotEmpty + ? Text('${state.currentWeather[2]} ${state.currentWeather[1]}°C') + : null, + trailing: state.isProcessing + ? const CircularProgressIndicator() + : IconButton.filledTonal( + onPressed: () { + logic.getWeather(); + }, + icon: const Icon(Icons.location_on), + ), + ), + ListTile( + title: const Text('分类'), + subtitle: state.categoryName.isNotEmpty ? Text(state.categoryName) : null, + trailing: IconButton.filledTonal( + onPressed: () { + showModalBottomSheet( + showDragHandle: true, + useSafeArea: true, + context: context, + builder: (context) { + return const CategoryAddComponent(); + }); + }, + icon: const Icon(Icons.category), + ), + ), + ListTile( + title: const Text('标签'), + subtitle: buildTagList(), + trailing: IconButton.filledTonal( + icon: const Icon(Icons.tag), + onPressed: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextField( + maxLines: 1, + controller: logic.tagTextEditingController, + decoration: InputDecoration( + fillColor: colorScheme.secondaryContainer, + border: const UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide.none, + ), + filled: true, + labelText: '标签', + ), + ), + actions: [ + TextButton( + onPressed: () { + logic.cancelAddTag(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () { + logic.addTag(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + ), + ), + ListTile( + title: const Text('心情指数'), + subtitle: Obx(() { + return Container( + constraints: const BoxConstraints(maxWidth: 400), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + MoodIconComponent(value: state.currentMoodRate.value), + Expanded( + child: Slider( + value: state.currentMoodRate.value, + divisions: 10, + label: '${(state.currentMoodRate.value * 100).toStringAsFixed(0)}%', + activeColor: + Color.lerp(const Color(0xFFFA4659), const Color(0xFF2EB872), state.currentMoodRate.value), + onChanged: (value) { + logic.changeRate(value); + }), + ), + ], + ), + ); + }), + ), + ListTile( + title: const Text('图片'), + subtitle: buildImage(), + ), + ListTile( + title: const Text('音频'), + subtitle: buildAudioPlayer(), + ), + ], + ); + } + + Widget buildWriting() { + return Column( + children: [ + TextField( + maxLines: 1, + controller: logic.titleTextEditingController, + decoration: const InputDecoration( + border: UnderlineInputBorder(), + contentPadding: EdgeInsets.all(12.0), + labelText: '标题', + ), + ), + Expanded( + child: QuillEditor.basic( + focusNode: logic.focusNode, + controller: logic.quillController, + configurations: const QuillEditorConfigurations( + padding: EdgeInsets.all(12.0), + placeholder: '正文', + sharedConfigurations: QuillSharedConfigurations(), + expands: true, + ), + ), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: QuillSimpleToolbar( + controller: logic.quillController, + configurations: const QuillSimpleToolbarConfigurations( + showFontFamily: false, + showFontSize: false, + showColorButton: false, + showBackgroundColorButton: false, + showAlignmentButtons: true, + showClipboardPaste: false, + showClipboardCut: false, + showClipboardCopy: false, + headerStyleType: HeaderStyleType.buttons, + sharedConfigurations: QuillSharedConfigurations(), + ), + ), + ), + ], + ); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: state.isNew == true ? const Text('新增日记') : const Text('编辑日记'), + actions: [ + IconButton( + icon: const Icon(Icons.check), + onPressed: () { + logic.unFocus(); + state.isNew ? logic.saveDiary() : logic.updateDiary(); + }, + tooltip: '保存', + ), + ], + ), + body: PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, value) { + if (didPop == true) { + return; + } else { + logic.handleBack(); + } + }, + child: TabBarView( + controller: logic.tabController, + physics: const NeverScrollableScrollPhysics(), + children: [buildWriting(), buildDetail()], + ), + ), + bottomNavigationBar: Obx(() { + return NavigationBar( + destinations: const [ + NavigationDestination( + icon: Icon(Icons.article_outlined), + label: '撰写', + selectedIcon: Icon(Icons.article), + ), + NavigationDestination( + icon: Icon(Icons.more_outlined), + label: '更多', + selectedIcon: Icon(Icons.more), + ) + ], + selectedIndex: state.tabIndex.value, + onDestinationSelected: (index) { + logic.selectTabView(index); + }, + labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, + ); + }), + ); + }, + ); + } +} diff --git a/lib/pages/font/font_logic.dart b/lib/pages/font/font_logic.dart new file mode 100644 index 0000000..ba50f45 --- /dev/null +++ b/lib/pages/font/font_logic.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'font_state.dart'; + +class FontLogic extends GetxController with GetSingleTickerProviderStateMixin { + final FontState state = FontState(); + + @override + void onInit() { + // TODO: implement onInit + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + void changeFontScale(value) { + state.fontScale = value; + update(); + } + + void changeWeight() { + update(); + } + + //保存设置 + Future saveFontScale() async { + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('确认修改'), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: const Text('取消')), + TextButton( + onPressed: () async { + Get.backLegacy(); + await Utils().prefUtil.setValue('fontScale', state.fontScale); + Get.forceAppUpdate(); + }, + child: const Text('确认')) + ], + ); + }); + } +} diff --git a/lib/pages/font/font_state.dart b/lib/pages/font/font_state.dart new file mode 100644 index 0000000..41b9efe --- /dev/null +++ b/lib/pages/font/font_state.dart @@ -0,0 +1,11 @@ +import 'package:mood_diary/utils/utils.dart'; + +class FontState { + late double fontScale; + + FontState() { + fontScale = (Utils().prefUtil.getValue('fontScale'))!; + + ///Initialize variables + } +} diff --git a/lib/pages/font/font_view.dart b/lib/pages/font/font_view.dart new file mode 100644 index 0000000..217e5bd --- /dev/null +++ b/lib/pages/font/font_view.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'font_logic.dart'; + +class FontPage extends StatelessWidget { + const FontPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final textStyle = Theme.of(context).textTheme; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text('字体大小'), + ), + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(top: 60), + child: Text( + '拖动下方滑块预览字体', + style: textStyle.titleMedium, + ), + ), + Text( + '黄水塘里游着白鸭,\n' + '高粱梗油青的刚高过头,\n' + '这跳动的心怎样安插,\n' + '田里一窄条路,八月里这忧愁?\n' + '天是昨夜雨洗过的,山岗\n' + '照着太阳又留一片影;\n' + '羊跟着放羊的转进村庄,\n' + '一大棵树荫下罩着井,又像是心!\n' + '从没有人说过八月什么话,\n' + '夏天过去了,也不到秋天。\n' + '但我望着田垄,土墙上的瓜,\n' + '仍不明白生活同梦怎样的连牵。', + textAlign: TextAlign.center, + textScaler: TextScaler.linear(state.fontScale), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + children: [ + Slider( + value: state.fontScale, + min: 0.8, + max: 1.2, + divisions: 4, + label: state.fontScale.toString(), + onChanged: (value) { + logic.changeFontScale(value); + }), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '小', + textScaler: TextScaler.linear(0.8), + ), + Text( + '标准', + textScaler: TextScaler.linear(1.0), + ), + Text( + '大', + textScaler: TextScaler.linear(1.2), + ), + ], + ), + ), + const SizedBox( + height: 60, + ) + ], + ), + ) + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + logic.saveFontScale(); + }, + child: const Icon(Icons.check), + ), + ); + }, + ); + } +} diff --git a/lib/pages/home/home_logic.dart b/lib/pages/home/home_logic.dart new file mode 100644 index 0000000..85aeb24 --- /dev/null +++ b/lib/pages/home/home_logic.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/view_mode.dart'; +import 'package:mood_diary/components/home_tab_view/home_tab_view_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'home_state.dart'; + +class HomeLogic extends GetxController with GetTickerProviderStateMixin { + final HomeState state = HomeState(); + + //fab动画控制器 + late AnimationController fabAnimationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + + //fab动画插值器 + late Animation fabAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: fabAnimationController, curve: Curves.easeInOut)); + + //tab控制器,默认全部分类,长度加一 + late TabController tabController = TabController(length: state.categoryList.length + 1, vsync: this); + + Set maxScrollExtentSet = {}; + + @override + void onInit() { + // TODO: implement onInit + + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + var innerController = state.nestedScrollKey.currentState!.innerController; + + innerController.addListener(() async { + if (!tabController.indexIsChanging) { + double offset = innerController.offset; + double maxScrollExtent = innerController.position.maxScrollExtent; + // 显示或隐藏“回到顶部”按钮 + state.isToTopShow.value = offset > 0; + // 分页加载 + if (offset == maxScrollExtent && !maxScrollExtentSet.contains(offset)) { + maxScrollExtentSet.add(offset); + HomeTabViewLogic homeTabViewLogic = Bind.find(tag: tabController.index.toString()); + await homeTabViewLogic.paginationDiary(homeTabViewLogic.state.diaryList.length, 10); + } + } + }); + + initTabListener(); + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + fabAnimationController.dispose(); + tabController.dispose(); + super.onClose(); + } + + void initTabListener() { + //监听tab滚动,切换时回到顶部 + + tabController.addListener(() async { + if (!tabController.indexIsChanging) { + var outerController = state.nestedScrollKey.currentState!.outerController; + if (outerController.offset != .0) { + await outerController.animateTo(0.0, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); + //标记为变化结束 + } + } + }); + } + + Future toTop() async { + var outerController = state.nestedScrollKey.currentState!.outerController; + await outerController.animateTo(0.0, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); + } + + //初始化分类tab + Future initCategoryTab() async { + //从数据库获取分类 + state.categoryList.value = await Utils().isarUtil.getAllCategoryAsync(); + //初始化tab控制器,长度加一由于有一个默认分类 + tabController = TabController(length: state.categoryList.length + 1, vsync: this); + } + + //重新获取当前页日历 + Future updateDiary() async { + //重新获取分类 + state.categoryList.value = await Utils().isarUtil.getAllCategoryAsync(); + //重新初始化Tab控制器 + var currentIndex = tabController.index; + //如果删除了最后一个,就往左移 + if (state.categoryList.length < currentIndex) { + currentIndex = state.categoryList.length; + } + //移出原有监听器 + tabController.removeListener(initTabListener); + tabController = TabController(length: state.categoryList.length + 1, vsync: this, initialIndex: currentIndex); + //重新开始监听 + initTabListener(); + maxScrollExtentSet = {}; + //添加帧回调保证对应logic已经被创建 + WidgetsBinding.instance.addPostFrameCallback((_) async { + HomeTabViewLogic homeTabViewLogic = Bind.find(tag: currentIndex.toString()); + await homeTabViewLogic.getDiary(); + }); + } + + //打开fab + Future openFab() async { + await HapticFeedback.selectionClick(); + state.isFabExpanded.value = true; + //开始动画 + await fabAnimationController.forward(); + } + + //关闭fab + Future closeFab() async { + await HapticFeedback.selectionClick(); + await fabAnimationController.reverse(); + state.isFabExpanded.value = false; + } + + //锁定屏幕 + void lockPage() { + //如果开启密码的同时开启了立即锁定,就直接跳转到锁屏页面 + if (Utils().prefUtil.getValue('lock') == true && Utils().prefUtil.getValue('lockNow') == true) { + Get.toNamed(AppRoutes.lockPage, arguments: 'pause'); + } + } + + void toUserPage() { + //如果已经登录 + if (Utils().supabaseUtil.user != null || Utils().supabaseUtil.session != null) { + Get.toNamed(AppRoutes.userPage); + } else { + Get.toNamed(AppRoutes.loginPage); + } + } + + //切换视图模式 + Future changeViewMode(ViewMode viewMode) async { + state.viewMode.value = viewMode.name; + await Utils().prefUtil.setValue('homeViewMode', viewMode.name); + } + + //新增一篇日记 + Future toEditPage() async { + //同时关闭fab + await HapticFeedback.selectionClick(); + //等待跳转,返回后刷新 + await Get.toNamed(AppRoutes.editPage, arguments: 'new'); + fabAnimationController.reset(); + state.isFabExpanded.value = false; + await updateDiary(); + //点击添加按钮后新增一篇日记 + } +} diff --git a/lib/pages/home/home_state.dart b/lib/pages/home/home_state.dart new file mode 100644 index 0000000..e2dd660 --- /dev/null +++ b/lib/pages/home/home_state.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class HomeState { + //分类列表,用于tab显示 + late RxList categoryList; + + //自定义标题名称,如果为空则为默认值 + late String customTitleName; + + //主滚动列表key + late GlobalKey nestedScrollKey; + + //视图模式状态 + late RxString viewMode; + + //fab展开状态 + late RxBool isFabExpanded; + + //回到顶部状态 + late RxBool isToTopShow; + + //日记的日期范围,默认为空表示全部 + late RxList selectDateTime; + + HomeState() { + isFabExpanded = false.obs; + isToTopShow = false.obs; + viewMode = Utils().prefUtil.getValue('homeViewMode')!.obs; + selectDateTime = [].obs; + categoryList = Utils().isarUtil.getAllCategory().obs; + customTitleName = Utils().prefUtil.getValue('customTitleName')!; + nestedScrollKey = GlobalKey(); + + ///Initialize variables + } +} diff --git a/lib/pages/home/home_view.dart b/lib/pages/home/home_view.dart new file mode 100644 index 0000000..a553321 --- /dev/null +++ b/lib/pages/home/home_view.dart @@ -0,0 +1,258 @@ +import 'dart:math'; + +import 'package:calendar_date_picker2/calendar_date_picker2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/view_mode.dart'; +import 'package:mood_diary/components/home_tab_view/home_tab_view_view.dart'; +import 'package:mood_diary/components/search_sheet/search_sheet_view.dart'; +import 'package:mood_diary/components/side_bar/side_bar_view.dart'; + +import 'home_logic.dart'; + +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final i18n = AppLocalizations.of(context)!; + //生成日历选择器 + Widget buildDatePicker() { + return CalendarDatePicker2( + config: CalendarDatePicker2Config( + calendarViewMode: CalendarDatePicker2Mode.day, + calendarType: CalendarDatePicker2Type.range, + ), + value: state.selectDateTime.value, + ); + } + + //生成tab + List buildTabBar() { + //默认的全部tab + var allTab = [const Tab(text: '全部')]; + //根据分类生成分类Tab + return allTab + + List.generate(state.categoryList.length, (index) { + return Tab(text: state.categoryList[index].categoryName); + }); + } + + //列表布局 + Widget buildListView() { + //全部日记页面 + var allView = [const HomeTabViewComponent(tag: '0')]; + //分类日记页面 + allView += List.generate(state.categoryList.length, (index) { + return HomeTabViewComponent( + categoryId: state.categoryList[index].id, + tag: (index + 1).toString(), + ); + }); + return TabBarView( + controller: logic.tabController, + physics: const NeverScrollableScrollPhysics(), + children: allView, + ); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + drawer: const SideBarComponent(), + drawerEnableOpenDragGesture: false, + body: Obx(() { + return Stack( + children: [ + NestedScrollView( + key: state.nestedScrollKey, + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverAppBar( + title: Tooltip( + message: '侧边栏', + child: InkWell( + onTap: () { + Scaffold.of(context).openDrawer(); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + state.customTitleName.isNotEmpty ? state.customTitleName : i18n.appName, + overflow: TextOverflow.ellipsis, + ), + ), + const Icon(Icons.chevron_right) + ], + ), + ), + ), + automaticallyImplyLeading: false, + actions: [ + IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + showDragHandle: true, + useSafeArea: true, + builder: (context) { + return const SearchSheetComponent(); + }); + }, + icon: const Icon(Icons.search), + tooltip: '搜索', + ), + PopupMenuButton( + offset: const Offset(0, 46), + tooltip: '更多', + itemBuilder: (context) { + return >[ + CheckedPopupMenuItem( + checked: state.viewMode.value == ViewMode.list.name, + onTap: () async { + await logic.changeViewMode(ViewMode.list); + }, + child: Text(i18n.homeViewModeList), + ), + CheckedPopupMenuItem( + checked: state.viewMode.value == ViewMode.calendar.name, + onTap: () async { + await logic.changeViewMode(ViewMode.calendar); + }, + child: Text(i18n.homeViewModeCalendar), + ), + ]; + }, + ), + ], + bottom: PreferredSize( + preferredSize: const Size.fromHeight(46.0), + child: Align( + alignment: Alignment.centerLeft, + child: TabBar( + controller: logic.tabController, + isScrollable: true, + dividerHeight: .0, + tabAlignment: TabAlignment.start, + indicatorSize: TabBarIndicatorSize.tab, + splashFactory: NoSplash.splashFactory, + indicator: + ShapeDecoration(shape: const StadiumBorder(), color: colorScheme.secondaryContainer), + indicatorWeight: .0, + indicatorPadding: const EdgeInsets.all(8.0), + tabs: buildTabBar(), + ), + ), + ), + ), + ]; + }, + body: state.viewMode.value == ViewMode.list.name ? buildListView() : buildDatePicker(), + ), + //如果fab打开了,显示一个蒙层 + if (state.isFabExpanded.value) ...[ + AnimatedBuilder( + animation: logic.fabAnimation, + builder: (context, child) { + return ModalBarrier( + color: Color.lerp(Colors.transparent, Colors.black54, logic.fabAnimation.value), + ); + }) + ] + ], + ); + }), + floatingActionButton: AnimatedBuilder( + animation: logic.fabAnimation, + builder: (context, child) { + return SizedBox( + height: 56 + (56 + 8), + child: Obx(() { + return Stack( + alignment: Alignment.bottomRight, + children: [ + if (state.isToTopShow.value && state.isFabExpanded.value == false) ...[ + Transform( + transform: Matrix4.identity()..translate(.0, -(56.0 + 8.0)), + alignment: FractionalOffset.center, + child: InkWell( + onTap: () async { + await logic.toTop(); + }, + child: Container( + decoration: ShapeDecoration( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16.0))), + color: colorScheme.tertiaryContainer), + width: 56.0, + height: 56.0, + child: const Icon(Icons.arrow_upward), + ), + ), + ) + ], + Transform( + transform: Matrix4.identity() + ..scale(pow(logic.fabAnimation.value, 2).toDouble(), logic.fabAnimation.value) + ..translate(.0, -((56.0 + 8.0)) * logic.fabAnimation.value), + alignment: FractionalOffset.centerRight, + child: InkWell( + onTap: () async { + await logic.toEditPage(); + }, + child: Container( + decoration: ShapeDecoration( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16.0))), + color: colorScheme.primaryContainer), + width: 140.0, + height: 56.0, + alignment: Alignment.center, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.edit, + color: colorScheme.onPrimaryContainer, + ), + const SizedBox( + width: 8.0, + ), + Text( + '新建日记', + style: TextStyle(color: colorScheme.onPrimaryContainer), + ), + ], + ), + ), + ), + ), + FloatingActionButton( + onPressed: () async { + state.isFabExpanded.value ? await logic.closeFab() : await logic.openFab(); + }, + backgroundColor: Color.lerp( + colorScheme.primaryContainer, colorScheme.tertiaryContainer, logic.fabAnimation.value), + child: Transform.rotate( + angle: 3 * pi / 4 * logic.fabAnimation.value, + child: const Icon(Icons.add), + ), + ) + ], + ); + }), + ); + }), + ); + }, + ); + } +} diff --git a/lib/pages/image/image_logic.dart b/lib/pages/image/image_logic.dart new file mode 100644 index 0000000..b9e690d --- /dev/null +++ b/lib/pages/image/image_logic.dart @@ -0,0 +1,24 @@ +import 'package:get/get.dart'; + +import 'image_state.dart'; + +class ImageLogic extends GetxController { + final ImageState state = ImageState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + void changePage(int index) { + state.imageIndex = index; + update(); + } +} diff --git a/lib/pages/image/image_state.dart b/lib/pages/image/image_state.dart new file mode 100644 index 0000000..c04f327 --- /dev/null +++ b/lib/pages/image/image_state.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class ImageState { + late List imageNameList; + + //当前图片的的位置 + late int imageIndex; + + //控制器 + late PageController pageController; + + ImageState() { + imageNameList = Get.arguments[0]; + imageIndex = Get.arguments[1]; + pageController = PageController(initialPage: imageIndex); + + ///Initialize variables + } +} diff --git a/lib/pages/image/image_view.dart b/lib/pages/image/image_view.dart new file mode 100644 index 0000000..e4e41a4 --- /dev/null +++ b/lib/pages/image/image_view.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; + +import 'image_logic.dart'; + +class ImagePage extends StatelessWidget { + const ImagePage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + return GetBuilder( + assignId: true, + init: logic, + builder: (logic) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: colorScheme.scrim.withOpacity(0.6), + title: Text( + '${state.imageIndex + 1}/${state.imageNameList.length}', + style: const TextStyle(color: Colors.white), + ), + iconTheme: const IconThemeData(color: Colors.white), + ), + body: PhotoViewGallery.builder( + scrollPhysics: const PageScrollPhysics(), + pageController: state.pageController, + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: FileImage(File(Utils().fileUtil.getRealPath('image', state.imageNameList[index]))), + heroAttributes: PhotoViewHeroAttributes(tag: index), + ); + }, + itemCount: state.imageNameList.length, + onPageChanged: (index) { + logic.changePage(index); + }, + loadingBuilder: (context, event) => Center( + child: SizedBox( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + value: event == null ? 0 : (event.cumulativeBytesLoaded / event.expectedTotalBytes!), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/laboratory/laboratory_logic.dart b/lib/pages/laboratory/laboratory_logic.dart new file mode 100644 index 0000000..a6263b7 --- /dev/null +++ b/lib/pages/laboratory/laboratory_logic.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'laboratory_state.dart'; + +class LaboratoryLogic extends GetxController { + final LaboratoryState state = LaboratoryState(); + late TextEditingController idTextEditingController = TextEditingController(); + late TextEditingController keyTextEditingController = TextEditingController(); + late TextEditingController qweatherTextEditingController = TextEditingController(); + + @override + void onReady() { + // TODO: implement onReady + initInfo(); + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + idTextEditingController.dispose(); + qweatherTextEditingController.dispose(); + keyTextEditingController.dispose(); + + super.onClose(); + } + + Future initInfo() async { + state.deviceInfo = await Utils().packageUtil.getInfo(); + update(); + } + + Future setTencentID() async { + if (idTextEditingController.text.isNotEmpty && keyTextEditingController.text.isNotEmpty) { + await Utils().prefUtil.setValue('tencentId', idTextEditingController.text); + await Utils().prefUtil.setValue('tencentKey', keyTextEditingController.text); + Get.backLegacy(); + } + } + + Future setQweatherKey() async { + if (qweatherTextEditingController.text.isNotEmpty) { + await Utils().prefUtil.setValue('qweatherKey', qweatherTextEditingController.text); + Get.backLegacy(); + } + } +} diff --git a/lib/pages/laboratory/laboratory_state.dart b/lib/pages/laboratory/laboratory_state.dart new file mode 100644 index 0000000..818d595 --- /dev/null +++ b/lib/pages/laboratory/laboratory_state.dart @@ -0,0 +1,22 @@ +import 'package:device_info_plus/device_info_plus.dart'; + +class LaboratoryState { + late AndroidDeviceInfo? androidInfo; + + late BaseDeviceInfo? deviceInfo; + + //分辨率 + late String display; + + //版本号 + late String version; + + LaboratoryState() { + display = ''; + version = ''; + androidInfo = null; + deviceInfo = null; + + ///Initialize variables + } +} diff --git a/lib/pages/laboratory/laboratory_view.dart b/lib/pages/laboratory/laboratory_view.dart new file mode 100644 index 0000000..cd2bacf --- /dev/null +++ b/lib/pages/laboratory/laboratory_view.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; + +import 'laboratory_logic.dart'; + +class LaboratoryPage extends StatelessWidget { + const LaboratoryPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + return GetBuilder( + assignId: true, + init: logic, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text('实验室'), + ), + body: ListView( + children: [ + ListTile( + title: const Text('系统版本号'), + onTap: null, + trailing: Text(state.version), + ), + ListTile( + title: const Text('腾讯云密钥'), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + maxLines: 1, + controller: logic.idTextEditingController, + decoration: const InputDecoration( + labelText: 'ID', + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 8.0, + ), + TextField( + maxLines: 1, + controller: logic.keyTextEditingController, + decoration: const InputDecoration( + labelText: 'KEY', + border: OutlineInputBorder(), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () { + logic.setTencentID(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + ), + ListTile( + title: const Text('和风天气密钥'), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextField( + maxLines: 1, + controller: logic.qweatherTextEditingController, + decoration: const InputDecoration( + labelText: 'Key', + border: OutlineInputBorder(), + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () { + logic.setQweatherKey(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + ) + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/lock/lock_logic.dart b/lib/pages/lock/lock_logic.dart new file mode 100644 index 0000000..773b3e7 --- /dev/null +++ b/lib/pages/lock/lock_logic.dart @@ -0,0 +1,79 @@ +import 'package:flutter/animation.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/router/app_routes.dart'; + +import 'lock_state.dart'; + +class LockLogic extends GetxController with GetSingleTickerProviderStateMixin { + final LockState state = LockState(); + late AnimationController animationController = + AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); + late Animation animation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animationController, curve: Curves.easeInOut)); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + animationController.dispose(); + super.onClose(); + } + + double interpolate(double x) { + var step = 10.0; + if (x <= 0.25) { + return 4 * step * x; + } else if (x <= 0.75) { + return step - 4 * step * (x - 0.25); + } else { + return -step + 4 * step * (x - 0.75); + } + } + + void deletePassword() { + if (state.password.value.isNotEmpty) { + state.password.value = state.password.value.substring(0, state.password.value.length - 1); + HapticFeedback.selectionClick(); + } + } + + Future updatePassword(String value) async { + if (state.password.value.length < 4) { + state.password.value += value; + HapticFeedback.selectionClick(); + } + Future.delayed(const Duration(milliseconds: 100), () async { + if (state.password.value.length == 4) { + //密码正确 + if (state.password.value == state.realPassword.value) { + checked(); + } else { + animationController.forward(); + await HapticFeedback.mediumImpact(); + Future.delayed(const Duration(milliseconds: 200), () { + animationController.reverse(); + state.password.value = ''; + }); + } + } + }); + } + + void checked() { + //如果是启动时的锁定,就跳转到homepage + if (state.lockType == null) { + Get.offAllNamed(AppRoutes.homePage); + } + + //如果是开启立即锁定后生命周期变化导致的锁定 + if (state.lockType == 'pause') { + Get.backLegacy(); + } + } +} diff --git a/lib/pages/lock/lock_state.dart b/lib/pages/lock/lock_state.dart new file mode 100644 index 0000000..388bfb7 --- /dev/null +++ b/lib/pages/lock/lock_state.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class LockState { + late RxString password; + + late RxString realPassword; + + //锁定类型,是立即锁定导致,还是启动锁定导致 + late String? lockType; + + LockState() { + password = ''.obs; + realPassword = Utils().prefUtil.getValue('password')!.obs; + lockType = Get.arguments; + + ///Initialize variables + } +} diff --git a/lib/pages/lock/lock_view.dart b/lib/pages/lock/lock_view.dart new file mode 100644 index 0000000..29d61c6 --- /dev/null +++ b/lib/pages/lock/lock_view.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'lock_logic.dart'; + +class LockPage extends StatelessWidget { + const LockPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + + final buttonSize = (textStyle.displayLarge!.fontSize! * textStyle.displayLarge!.height!); + Widget buildNumButton(String num) { + return Ink( + decoration: BoxDecoration(color: colorScheme.surfaceContainerHighest, shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () async { + await logic.updatePassword(num); + }, + child: Center( + child: Text( + num, + style: textStyle.displaySmall, + )), + ), + ); + } + + Widget buildDeleteButton() { + return Ink( + decoration: const BoxDecoration(shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () { + logic.deletePassword(); + }, + child: const Icon( + Icons.backspace, + ), + ), + ); + } + + Widget buildBiometricsButton() { + return Ink( + decoration: const BoxDecoration(shape: BoxShape.circle), + child: InkWell( + borderRadius: BorderRadius.all(Radius.circular(buttonSize / 2)), + onTap: () async { + if (await Utils().authUtil.check()) { + logic.checked(); + } + }, + child: const Icon( + Icons.fingerprint, + ), + ), + ); + } + + List buildPasswordIndicator() { + return List.generate(4, (index) { + return Obx(() { + return Icon( + Icons.circle, + size: 16, + color: Color.lerp( + state.password.value.length > index ? colorScheme.onSurface : colorScheme.surfaceContainerHighest, + Colors.red, + logic.animation.value), + ); + }); + }); + } + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + ), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.lock), + const SizedBox( + height: 16.0, + ), + Text( + '请输入密码', + style: textStyle.titleMedium, + ), + const SizedBox( + height: 16.0, + ), + AnimatedBuilder( + animation: logic.animation, + builder: (context, child) { + return Transform.translate( + offset: Offset(logic.interpolate(logic.animation.value), 0), + child: Wrap( + spacing: 16.0, + children: buildPasswordIndicator(), + ), + ); + }, + ), + const SizedBox( + height: 32.0, + ), + SizedBox( + width: buttonSize * 3 + 20, + height: buttonSize * 4 + 30, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.0, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), + children: [ + buildNumButton('1'), + buildNumButton('2'), + buildNumButton('3'), + buildNumButton('4'), + buildNumButton('5'), + buildNumButton('6'), + buildNumButton('7'), + buildNumButton('8'), + buildNumButton('9'), + buildBiometricsButton(), + buildNumButton('0'), + buildDeleteButton() + ], + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/login/login_logic.dart b/lib/pages/login/login_logic.dart new file mode 100644 index 0000000..4ae0f6d --- /dev/null +++ b/lib/pages/login/login_logic.dart @@ -0,0 +1,25 @@ +import 'package:get/get.dart'; + +import 'login_state.dart'; + +class LoginLogic extends GetxController { + final LoginState state = LoginState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + //切换登录注册 + void changeForm() { + state.isLogin = !state.isLogin; + update(); + } +} diff --git a/lib/pages/login/login_state.dart b/lib/pages/login/login_state.dart new file mode 100644 index 0000000..6cb752c --- /dev/null +++ b/lib/pages/login/login_state.dart @@ -0,0 +1,11 @@ +class LoginState { + //登录注册页面切换 + late bool isLogin; + + LoginState() { + //默认为登录 + isLogin = true; + + ///Initialize variables + } +} diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart new file mode 100644 index 0000000..bf512e5 --- /dev/null +++ b/lib/pages/login/login_view.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/login_form/login_form_view.dart'; +import 'package:mood_diary/components/register_form/register_form_view.dart'; + +import 'login_logic.dart'; + +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: state.isLogin ? const Text('登录') : const Text('注册'), + ), + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: state.isLogin ? const LoginFormComponent() : const RegisterFormComponent(), + )); + }, + ); + } +} diff --git a/lib/pages/privacy/privacy_logic.dart b/lib/pages/privacy/privacy_logic.dart new file mode 100644 index 0000000..b0fbbb6 --- /dev/null +++ b/lib/pages/privacy/privacy_logic.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; + +import 'privacy_state.dart'; + +class PrivacyLogic extends GetxController { + final PrivacyState state = PrivacyState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } +} diff --git a/lib/pages/privacy/privacy_state.dart b/lib/pages/privacy/privacy_state.dart new file mode 100644 index 0000000..234fbe9 --- /dev/null +++ b/lib/pages/privacy/privacy_state.dart @@ -0,0 +1,5 @@ +class PrivacyState { + PrivacyState() { + ///Initialize variables + } +} diff --git a/lib/pages/privacy/privacy_view.dart b/lib/pages/privacy/privacy_view.dart new file mode 100644 index 0000000..6cff73d --- /dev/null +++ b/lib/pages/privacy/privacy_view.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:markdown_widget/markdown_widget.dart'; + +class PrivacyPage extends StatelessWidget { + const PrivacyPage({super.key}); + + @override + Widget build(BuildContext context) { + // final logic = Bind.find(); + // final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + return Scaffold( + appBar: AppBar( + title: const Text('隐私政策'), + ), + body: MarkdownWidget( + data: '''# 隐私政策 + +更新日期:**2024/6/16** + +生效日期:**2024/6/16** + +## 导言 + +*心绪日记* 是一款由 *江有汜* (以下简称“我们”)提供的产品。 您在使用我们的服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明,在使用我们的服务时,我们如何收集、使用、储存和分享这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。 本《隐私政策》与您所使用的 *心绪日记* 服务息息相关,希望您仔细阅读,在需要时,按照本《隐私政策》的指引,作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇,我们尽量以简明扼要的表述,并提供进一步说明的链接,以便您的理解。 + +**您使用或继续使用我们的服务,即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。** + +如对本《隐私政策》或相关事宜有任何问题,请通过 **1624109111@qq.com** 与我们联系。 + +## 1. 我们收集的信息 + +我们或我们的第三方合作伙伴提供服务时,可能会收集、储存和使用下列与您有关的信息。如果您不提供相关信息,可能无法注册成为我们的用户或无法享受我们提供的某些服务,或者无法达到相关服务拟达到的效果。 + ++ **位置信息**,指您开启设备定位功能并使用我们基于位置提供的相关服务时,收集的有关您位置的信息,包括: + + 您通过具有定位功能的移动设备使用我们的服务时,通过GPS或WiFi等方式收集的您的地理位置信息; + + 您可以通过关闭定位功能,停止对您的地理位置信息的收集。 ++ **日志信息**,指您使用我们的服务时,系统可能通过cookies、标识符及相关技术收集的信息,包括您的**设备信息**、**浏览信息**、**点击信息**,并将该等信息储存为日志信息,为您提供个性化的用户体验、保障服务安全。您可以通过浏览器设置拒绝或管理cookie、标识符或相关技术的使用。 + +## 2. 信息的存储 + +**2.1 信息存储的方式和期限** + ++ 我们会通过安全的方式存储您的信息,包括本地存储(例如利用APP进行数据缓存)、数据库和服务器日志。 ++ 一般情况下,我们只会在为实现服务目的所必需的时间内或法律法规规定的条件下存储您的个人信息。 + +**2.2 信息存储的地域** + ++ 我们会按照法律法规规定,将境内收集的用户个人信息存储于中国境内。 ++ 目前我们不会跨境传输或存储您的个人信息。将来如需跨境传输或存储的,我们会向您告知信息出境的目的、接收方、安全保证措施和安全风险,并征得您的同意。 + +**2.3 产品或服务停止运营时的通知** + ++ 当我们的产品或服务发生停止运营的情况时,我们将以推送通知、公告等形式通知您,并在合理期限内删除您的个人信息或进行匿名化处理,法律法规另有规定的除外。 + +## 3. 信息安全 + +我们使用各种安全技术和程序,以防信息的丢失、不当使用、未经授权阅览或披露。例如,在某些服务中,我们将利用加密技术(例如SSL)来保护您提供的个人信息。但请您理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。您需要了解,您接入我们的服务所用的系统和通讯网络,有可能因我们可控范围外的因素而出现问题。 + +## 4. 我们如何使用信息 + +我们可能将在向您提供服务的过程之中所收集的信息用作下列用途: + ++ 向您提供服务; ++ 在我们提供服务时,用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途,确保我们向您提供的产品和服务的安全性; ++ 帮助我们设计新服务,改善我们现有服务; ++ 使我们更加了解您如何接入和使用我们的服务,从而针对性地回应您的个性化需求,例如语言设定、位置设定、个性化的帮助服务和指示,或对您和其他用户作出其他方面的回应; ++ 向您提供与您更加相关的广告以替代普遍投放的广告; ++ 评估我们服务中的广告和其他促销及推广活动的效果,并加以改善; ++ 软件认证或管理软件升级; ++ 让您参与有关我们产品和服务的调查。 + +## 5. 信息共享 + +目前,我们不会主动共享或转让您的个人信息至第三方,如存在其他共享或转让您的个人信息或您需要我们将您的个人信息共享或转让至第三方情形时,我们会直接或确认第三方征得您对上述行为的明示同意。 + +为了投放广告,评估、优化广告投放效果等目的,我们需要向广告主及其代理商等第三方合作伙伴共享您的部分数据,要求其严格遵守我们关于数据隐私保护的措施与要求,包括但不限于根据数据保护协议、承诺书及相关数据处理政策进行处理,避免识别出个人身份,保障隐私安全。 + +我们不会向合作伙伴分享可用于识别您个人身份的信息(例如您的姓名或电子邮件地址),除非您明确授权。 + +我们不会对外公开披露所收集的个人信息,如必须公开披露时,我们会向您告知此次公开披露的目的、披露信息的类型及可能涉及的敏感信息,并征得您的明示同意。 + +随着我们业务的持续发展,我们有可能进行合并、收购、资产转让等交易,我们将告知您相关情形,按照法律法规及不低于本《隐私政策》所要求的标准继续保护或要求新的控制者继续保护您的个人信息。 + +另外,根据相关法律法规及国家标准,以下情形中,我们可能会共享、转让、公开披露个人信息无需事先征得您的授权同意: + ++ 与国家安全、国防安全直接相关的; ++ 与公共安全、公共卫生、重大公共利益直接相关的; ++ 犯罪侦查、起诉、审判和判决执行等直接相关的; ++ 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的; ++ 个人信息主体自行向社会公众公开个人信息的; ++ 从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。 + +## 6. 您的权利 + +在您使用我们的服务期间,我们可能会视产品具体情况为您提供相应的操作设置,以便您可以查询、删除、更正或撤回您的相关个人信息,您可参考相应的具体指引进行操作。此外,我们还设置了投诉举报渠道,您的意见将会得到及时的处理。如果您无法通过上述途径和方式行使您的个人信息主体权利,您可以通过本《隐私政策》中提供的联系方式提出您的请求,我们会按照法律法规的规定予以反馈。 + +## 7. 变更 + +我们可能适时修订本《隐私政策》的条款。当变更发生时,我们会在版本更新时向您提示新的《隐私政策》,并向您说明生效日期。请您仔细阅读变更后的《隐私政策》内容,**若您继续使用我们的服务,即表示您同意我们按照更新后的《隐私政策》处理您的个人信息。** + +## 8. 未成年人保护 + +我们鼓励父母或监护人指导未满十八岁的未成年人使用我们的服务。我们建议未成年人鼓励他们的父母或监护人阅读本《隐私政策》,并建议未成年人在提交的个人信息之前寻求父母或监护人的同意和指导。''', + selectable: true, + padding: const EdgeInsets.symmetric(horizontal: 10.0), + config: colorScheme.brightness == Brightness.dark ? MarkdownConfig.darkConfig : MarkdownConfig.defaultConfig, + ), + ); + } +} diff --git a/lib/pages/recycle/recycle_logic.dart b/lib/pages/recycle/recycle_logic.dart new file mode 100644 index 0000000..ca83b31 --- /dev/null +++ b/lib/pages/recycle/recycle_logic.dart @@ -0,0 +1,62 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'recycle_state.dart'; + +class RecycleLogic extends GetxController { + final RecycleState state = RecycleState(); + late final homeLogic = Bind.find(); + + @override + void onReady() { + // TODO: implement onReady + getDiaryList(); + super.onReady(); + } + + Future getDiaryList() async { + state.diaryList = await Utils().isarUtil.getRecycleBinDiaries(); + update(); + } + + //长按卡片删除 + Future deleteDiary(index) async { + //删除日记 + if (await Utils().isarUtil.deleteADiary(state.diaryList[index].id)) { + for (var name in state.diaryList[index].imageName) { + Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('image', name)); + } + for (var name in state.diaryList[index].audioName) { + Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('audio', name)); + } + for (var name in state.diaryList[index].videoName) { + Utils().fileUtil.deleteFile(Utils().fileUtil.getRealPath('video', name)); + } + Utils().noticeUtil.showToast('删除成功'); + //重新获取 + getDiaryList(); + } + } + + //重新显示 + Future showDiary(Diary diary) async { + //将show置为true + diary.show = true; + //写入数据库 + await Utils().isarUtil.updateADiary(diary); + //重新获取 + getDiaryList(); + update(); + Utils().noticeUtil.showToast('已恢复'); + } + + @override + void onClose() { + // TODO: implement onClose + //退出后调用主页刷新 + + super.onClose(); + } +} diff --git a/lib/pages/recycle/recycle_state.dart b/lib/pages/recycle/recycle_state.dart new file mode 100644 index 0000000..bafd426 --- /dev/null +++ b/lib/pages/recycle/recycle_state.dart @@ -0,0 +1,12 @@ +import 'package:mood_diary/common/models/isar/diary.dart'; + +class RecycleState { + //回收站日记数组 + late List diaryList; + + RecycleState() { + diaryList = []; + + ///Initialize variables + } +} diff --git a/lib/pages/recycle/recycle_view.dart b/lib/pages/recycle/recycle_view.dart new file mode 100644 index 0000000..0f29ac2 --- /dev/null +++ b/lib/pages/recycle/recycle_view.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import 'recycle_logic.dart'; + +class RecyclePage extends StatelessWidget { + const RecyclePage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final colorScheme = Theme.of(context).colorScheme; + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: const Text( + '回收站', + ), + ), + body: ListView.builder( + itemBuilder: (context, index) { + return ListTile( + onTap: null, + title: Text(state.diaryList[index].time.toString().split('.')[0]), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Column( + children: [ + IconButton( + onPressed: () { + logic.showDiary(state.diaryList[index]); + }, + icon: const Icon(Icons.settings_backup_restore), + style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + ), + const Text('恢复'), + ], + ), + Column( + children: [ + IconButton( + onPressed: () { + logic.deleteDiary(index); + }, + icon: const Icon(Icons.delete_forever), + style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), + color: colorScheme.error, + ), + Text( + '删除', + style: TextStyle(color: colorScheme.error), + ), + ], + ) + ], + ), + ); + }, + itemCount: state.diaryList.length, + ), + ); + }, + ); + } +} diff --git a/lib/pages/setting/setting_logic.dart b/lib/pages/setting/setting_logic.dart new file mode 100644 index 0000000..67fdc03 --- /dev/null +++ b/lib/pages/setting/setting_logic.dart @@ -0,0 +1,150 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:share_plus/share_plus.dart'; + +import 'setting_state.dart'; + +class SettingLogic extends GetxController { + final SettingState state = SettingState(); + late final homeLogic = Bind.find(); + + late TextEditingController textEditingController = TextEditingController(text: state.customTitle.value); + + @override + void onInit() { + // TODO: implement onInit + getDataUsage(); + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + textEditingController.dispose(); + + super.onClose(); + } + + //获取当前占用储存空间 + Future getDataUsage() async { + var sizeMap = await Utils().fileUtil.countSize(); + state.dataUsage.value = '${sizeMap['size']} ${sizeMap['unit']}'; + if (sizeMap['bytes'] > (1024 * 1024 * 100)) { + await deleteCache(); + Utils().noticeUtil.showToast('缓存已自动清理'); + } + } + + Future deleteCache() async { + await Utils().fileUtil.clearCache(); + await getDataUsage(); + Utils().noticeUtil.showToast('清理成功'); + } + + //动态配色 + Future dynamicColor(bool value) async { + await Utils().prefUtil.setValue('dynamicColor', value); + state.dynamicColor.value = value; + } + + //图片质量 + Future quality(int value) async { + Get.backLegacy(); + await Utils().prefUtil.setValue('quality', value); + state.quality.value = value; + } + + //本地化 + Future local(bool value) async { + await Utils().prefUtil.setValue('local', value); + state.local.value = value; + } + + //获取天气 + Future weather(bool value) async { + if (await Utils().permissionUtil.checkPermission(Permission.location)) { + await Utils().prefUtil.setValue('getWeather', value); + state.getWeather.value = value; + } + } + + //立即锁定 + Future lockNow(bool value) async { + await Utils().prefUtil.setValue('lockNow', value); + state.lockNow.value = value; + } + + //进入回收站 + void toRecyclePage() { + Get.toNamed(AppRoutes.recyclePage); + } + + //进入字体大小设置页 + void toFontSizePage() { + Get.toNamed(AppRoutes.fontPage); + } + + void toLaboratoryPage() { + Get.toNamed(AppRoutes.laboratoryPage); + } + + void cancelCustomTitle() { + textEditingController.clear(); + Get.backLegacy(); + } + + Future setCustomTitle() async { + if (textEditingController.text.isNotEmpty) { + state.customTitle.value = textEditingController.text; + await Utils().prefUtil.setValue('customTitleName', textEditingController.text); + Get.backLegacy(); + textEditingController.clear(); + Utils().noticeUtil.showToast('重启应用后生效'); + } + } + + //更改字体 + Future changeFontTheme(int value) async { + Get.backLegacy(); + await Utils().prefUtil.setValue('fontTheme', value); + state.fontTheme.value = value; + Get.changeTheme(await Utils().themeUtil.buildTheme(Brightness.light)); + Get.changeTheme(await Utils().themeUtil.buildTheme(Brightness.dark)); + } + + Future exportFile() async { + Get.backLegacy(); + Utils().noticeUtil.showToast('正在处理中'); + final dataPath = Utils().fileUtil.getRealPath('', ''); + final zipPath = Utils().fileUtil.getCachePath(''); + final isolateParams = {'zipPath': zipPath, 'dataPath': dataPath}; + var path = await compute(Utils().fileUtil.zipFile, isolateParams); + await Share.shareXFiles([XFile(path)]); + } + + //导入 + Future import() async { + Get.backLegacy(); + FilePickerResult? result = await FilePicker.platform.pickFiles(allowedExtensions: ['zip'], type: FileType.custom); + if (result != null) { + Utils().noticeUtil.showToast('数据导入中,请不要离开页面'); + await Utils().fileUtil.extractFile(result.files.single.path!); + Utils().noticeUtil.showToast('导入成功'); + } else { + Utils().noticeUtil.showToast('取消文件选择'); + } + } +} diff --git a/lib/pages/setting/setting_state.dart b/lib/pages/setting/setting_state.dart new file mode 100644 index 0000000..fccba4d --- /dev/null +++ b/lib/pages/setting/setting_state.dart @@ -0,0 +1,41 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class SettingState { + //当前占用空间 + late RxString dataUsage; + + late RxInt themeMode; + + late RxInt color; + + late RxBool dynamicColor; + late RxInt quality; + + late RxInt fontTheme; + + late RxBool getWeather; + late RxBool lock; + + late RxBool lockNow; + + late RxBool local; + + late RxString customTitle; + + SettingState() { + dataUsage = ''.obs; + themeMode = Utils().prefUtil.getValue('themeMode')!.obs; + color = Utils().prefUtil.getValue('color')!.obs; + dynamicColor = Utils().prefUtil.getValue('dynamicColor')!.obs; + quality = Utils().prefUtil.getValue('quality')!.obs; + fontTheme = Utils().prefUtil.getValue('fontTheme')!.obs; + getWeather = Utils().prefUtil.getValue('getWeather')!.obs; + lock = Utils().prefUtil.getValue('lock')!.obs; + lockNow = Utils().prefUtil.getValue('lockNow')!.obs; + local = Utils().prefUtil.getValue('local')!.obs; + customTitle = Utils().prefUtil.getValue('customTitleName')!.obs; + + ///Initialize variables + } +} diff --git a/lib/pages/setting/setting_view.dart b/lib/pages/setting/setting_view.dart new file mode 100644 index 0000000..9abdd87 --- /dev/null +++ b/lib/pages/setting/setting_view.dart @@ -0,0 +1,513 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/components/color_dialog/color_dialog_view.dart'; +import 'package:mood_diary/components/dashboard/dashboard_view.dart'; +import 'package:mood_diary/components/remove_password/remove_password_view.dart'; +import 'package:mood_diary/components/set_password/set_password_view.dart'; +import 'package:mood_diary/components/theme_mode_dialog/theme_mode_dialog_view.dart'; + +import 'setting_logic.dart'; + +class SettingPage extends StatelessWidget { + const SettingPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final textStyle = Theme.of(context).textTheme; + final colorScheme = Theme.of(context).colorScheme; + final i18n = AppLocalizations.of(context)!; + + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar( + title: Text(i18n.settingTitle), + ), + body: ListView( + padding: const EdgeInsets.fromLTRB(8.0, .0, 8.0, 8.0), + children: [ + ListTile( + title: Text( + '管理', + style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + const DashboardComponent(), + ListTile( + title: Text( + i18n.settingData, + style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + Card.filled( + color: colorScheme.surfaceContainer, + child: Column( + children: [ + ListTile( + title: Text(i18n.settingRecycle), + trailing: const Icon(Icons.chevron_right), + onTap: () { + logic.toRecyclePage(); + }, + leading: const Icon(Icons.delete_outline), + ), + ListTile( + title: Text(i18n.settingExport), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(i18n.settingExportDialogTitle), + content: Text(i18n.settingExportDialogContent), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () async { + await logic.exportFile(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + trailing: const Icon(Icons.chevron_right), + leading: const Icon(Icons.file_upload_outlined), + ), + ListTile( + title: Text(i18n.settingImport), + subtitle: Text(i18n.settingImportDes), + onTap: () async { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(i18n.settingImportDialogTitle), + content: Text(i18n.settingImportDialogContent), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () async { + logic.import(); + }, + child: Text(i18n.settingImportSelectFile)) + ], + ); + }); + }, + trailing: const Icon(Icons.chevron_right), + leading: const Icon(Icons.file_download_outlined), + ), + ListTile( + title: Text(i18n.settingClean), + leading: const Icon(Icons.cleaning_services_outlined), + trailing: Obx(() { + return Text( + state.dataUsage.value, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ); + }), + onTap: () { + logic.deleteCache(); + }, + ), + ], + ), + ), + ListTile( + title: Text( + i18n.settingDisplay, + style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + Card.filled( + color: colorScheme.surfaceContainer, + child: Column( + children: [ + ListTile( + title: Text(i18n.settingThemeMode), + leading: const Icon(Icons.invert_colors), + trailing: Obx(() { + return Text( + switch (state.themeMode.value) { + 0 => i18n.themeModeSystem, + 1 => i18n.themeModeLight, + 2 => i18n.themeModeDark, + // TODO: Handle this case. + int() => throw UnimplementedError() + }, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ); + }), + onTap: () { + showDialog( + context: context, + builder: (context) { + return const ThemeModeDialogComponent(); + }); + }, + ), + ListTile( + title: Text(i18n.settingColor), + leading: const Icon(Icons.color_lens_outlined), + trailing: Obx(() { + return Text( + switch (state.color.value) { + 0 => i18n.colorNameBaiCaoShuang, + 1 => i18n.colorNameZhuYue, + 2 => i18n.colorNameLvLiuLi, + 3 => i18n.colorNameJin, + 4 => i18n.colorNameShiYangJin, + _ => i18n.colorNameSystem + }, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ); + }), + onTap: () { + showDialog( + context: context, + builder: (context) { + return const ColorDialogComponent(); + }); + }, + ), + ListTile( + title: Text(i18n.settingImageQuality), + subtitle: Text(i18n.settingImageQualityDes), + leading: const Icon(Icons.gradient_outlined), + trailing: Obx(() { + return Text( + switch (state.quality.value) { + 0 => i18n.qualityLow, + 1 => i18n.qualityMedium, + 2 => i18n.qualityHigh, + // TODO: Handle this case. + int() => throw UnimplementedError(), + }, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ); + }), + onTap: () { + showDialog( + context: context, + builder: (context) { + return Obx(() { + return SimpleDialog( + title: Text(i18n.settingImageQuality), + children: [ + SimpleDialogOption( + child: Row( + children: [ + Text(i18n.qualityLow), + const SizedBox( + width: 10, + ), + if (state.quality.value == 0) ...[ + const Icon(Icons.check), + ], + ], + ), + onPressed: () { + logic.quality(0); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + Text(i18n.qualityMedium), + const SizedBox( + width: 10, + ), + if (state.quality.value == 1) ...[ + const Icon(Icons.check), + ], + ], + ), + onPressed: () { + logic.quality(1); + }, + ), + SimpleDialogOption( + child: Row( + children: [ + Text(i18n.qualityHigh), + const SizedBox( + width: 10, + ), + if (state.quality.value == 2) ...[ + const Icon(Icons.check), + ], + ], + ), + onPressed: () { + logic.quality(2); + }, + ), + ], + ); + }); + }); + }, + ), + ListTile( + title: Text(i18n.settingFontSize), + leading: const Icon(Icons.format_size_outlined), + trailing: const Icon(Icons.chevron_right), + onTap: () { + logic.toFontSizePage(); + }, + ), + // ListTile( + // title: Text(i18n.settingFontStyle), + // leading: const Icon(Icons.text_format_outlined), + // trailing: Obx(() { + // return Text( + // switch (state.fontTheme.value) { + // 0 => '思源黑体', + // 1 => '思源宋体', + // // TODO: Handle this case. + // int() => throw UnimplementedError(), + // }, + // style: textStyle.bodySmall!.copyWith( + // color: colorScheme.primary, + // ), + // ); + // }), + // onTap: () { + // showDialog( + // context: context, + // builder: (context) { + // return Obx(() { + // return SimpleDialog( + // title: Text(i18n.settingFontStyle), + // children: [ + // SimpleDialogOption( + // child: Row( + // children: [ + // const Text( + // '思源黑体', + // style: TextStyle( + // fontFamily: 'NotoSans SC', fontFamilyFallback: ['NotoSans SC']), + // ), + // const SizedBox( + // width: 10, + // ), + // if (state.fontTheme.value == 0) ...[ + // const Icon(Icons.check), + // ] + // ], + // ), + // onPressed: () { + // logic.changeFontTheme(0); + // }, + // ), + // SimpleDialogOption( + // child: Row( + // children: [ + // const Text( + // '思源宋体', + // style: TextStyle( + // fontFamily: 'NotoSerif SC', fontFamilyFallback: ['NotoSerif SC']), + // ), + // const SizedBox( + // width: 10, + // ), + // if (state.fontTheme.value == 1) ...[ + // const Icon(Icons.check), + // ] + // ], + // ), + // onPressed: () { + // logic.changeFontTheme(1); + // }, + // ), + // ], + // ); + // }); + // }); + // }, + // ), + Obx(() { + return SwitchListTile( + value: state.getWeather.value, + onChanged: (value) { + logic.weather(value); + }, + title: Text(i18n.settingWeather), + secondary: const Icon(Icons.sunny), + ); + }), + ListTile( + title: const Text('自定义首页名称'), + leading: const Icon(Icons.drive_file_rename_outline), + trailing: Obx(() { + return Text( + state.customTitle.value, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ); + }), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: TextField( + maxLines: 1, + controller: logic.textEditingController, + decoration: InputDecoration( + fillColor: colorScheme.secondaryContainer, + border: const UnderlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide.none, + ), + filled: true, + labelText: '名称', + ), + ), + actions: [ + TextButton( + onPressed: () { + logic.cancelCustomTitle(); + }, + child: Text(i18n.cancel)), + TextButton( + onPressed: () async { + await logic.setCustomTitle(); + }, + child: Text(i18n.ok)) + ], + ); + }); + }, + ) + ], + ), + ), + ListTile( + title: Text( + i18n.settingPrivacy, + style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + Card.filled( + color: colorScheme.surfaceContainer, + child: Column( + children: [ + Obx(() { + return SwitchListTile( + value: state.local.value, + onChanged: (value) { + logic.local(value); + }, + title: Text(i18n.settingLocal), + subtitle: Text(i18n.settingLocalDes), + secondary: const Icon(Icons.cloud_off_outlined), + ); + }), + Obx(() { + return ListTile( + trailing: Text( + state.lock.value ? i18n.settingLockOpen : i18n.settingLockNotOpen, + style: textStyle.bodySmall!.copyWith( + color: colorScheme.primary, + ), + ), + onTap: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(i18n.settingLock), + content: Text( + state.lock.value ? i18n.settingLockResetLock : i18n.settingLockChooseLockType), + actions: [ + TextButton( + onPressed: () { + Get.backLegacy(); + showModalBottomSheet( + context: context, + isScrollControlled: true, + showDragHandle: true, + useSafeArea: true, + builder: (context) { + return state.lock.value + ? const RemovePasswordComponent() + : const SetPasswordComponent(); + }); + }, + child: Text(state.lock.value ? '关闭' : '数字')), + ], + ); + }); + }, + title: Text(i18n.settingLock), + leading: const Icon(Icons.lock_outline), + ); + }), + Obx(() { + return SwitchListTile( + value: state.lockNow.value, + onChanged: state.lock.value + ? (value) { + logic.lockNow(value); + } + : null, + title: Text(i18n.settingLockNow), + subtitle: Text(i18n.settingLockNowDes), + secondary: const Icon(Icons.lock_clock_outlined), + ); + }), + ], + ), + ), + ListTile( + title: Text( + i18n.settingMore, + style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ), + Card.filled( + color: colorScheme.surfaceContainer, + child: Column( + children: [ + ListTile( + title: Text(i18n.settingLab), + leading: const Icon(Icons.science_outlined), + trailing: const Icon(Icons.chevron_right), + onTap: () { + logic.toLaboratoryPage(); + }, + ), + ], + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages/share/share_logic.dart b/lib/pages/share/share_logic.dart new file mode 100644 index 0000000..8aa1852 --- /dev/null +++ b/lib/pages/share/share_logic.dart @@ -0,0 +1,44 @@ +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:get/get.dart'; +import 'package:share_plus/share_plus.dart'; + +import 'share_state.dart'; + +class ShareLogic extends GetxController { + final ShareState state = ShareState(); + + @override + void onInit() { + // TODO: implement onInit + state.diary = Get.arguments; + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future captureWidget() async { + var boundary = state.key.currentContext?.findRenderObject() as RenderRepaintBoundary; + var pixelRatio = MediaQuery.devicePixelRatioOf(Get.context!); + var image = await boundary.toImage(pixelRatio: pixelRatio); + var data = await image.toByteData(format: ImageByteFormat.png); + return data!.buffer.asUint8List(); + } + + Future share() async { + await Share.shareXFiles([XFile.fromData(await captureWidget(), mimeType: 'image/png')]); + } +} diff --git a/lib/pages/share/share_state.dart b/lib/pages/share/share_state.dart new file mode 100644 index 0000000..cbf459f --- /dev/null +++ b/lib/pages/share/share_state.dart @@ -0,0 +1,14 @@ +import 'package:flutter/cupertino.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; + +class ShareState { + late GlobalKey key; + + late Diary diary; + + ShareState() { + key = GlobalKey(); + + ///Initialize variables + } +} diff --git a/lib/pages/share/share_view.dart b/lib/pages/share/share_view.dart new file mode 100644 index 0000000..f1b7788 --- /dev/null +++ b/lib/pages/share/share_view.dart @@ -0,0 +1,140 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/icons.dart'; +import 'package:mood_diary/components/mood_icon/mood_icon_view.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'share_logic.dart'; + +class SharePage extends StatelessWidget { + const SharePage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + final textStyle = Theme.of(context).textTheme; + var imageColor = state.diary.imageColor; + final colorScheme = imageColor != null + ? ColorScheme.fromSeed(seedColor: Color(imageColor), brightness: Theme.of(context).brightness) + : Theme.of(context).colorScheme; + const cardSize = 300.0; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Theme( + data: imageColor != null + ? Theme.of(context).copyWith( + colorScheme: + ColorScheme.fromSeed(seedColor: Color(imageColor), brightness: Theme.of(context).brightness)) + : Theme.of(context), + child: Scaffold( + appBar: AppBar( + title: Text( + i18n.shareTitle, + ), + ), + extendBodyBehindAppBar: true, + body: Center( + child: RepaintBoundary( + key: state.key, + child: Container( + decoration: BoxDecoration( + color: colorScheme.surfaceContainerLow, + boxShadow: [ + BoxShadow( + color: colorScheme.shadow.withOpacity(0.5), + blurRadius: 5.0, + offset: const Offset(5.0, 10.0), + ) + ], + ), + width: cardSize, + height: cardSize * 1.618, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (state.diary.imageName.isNotEmpty) ...[ + Container( + height: cardSize * 1.618 * 0.618, + decoration: BoxDecoration( + image: DecorationImage( + image: + FileImage(File(Utils().fileUtil.getRealPath('image', state.diary.imageName.first))), + fit: BoxFit.cover), + ), + ) + ], + Expanded( + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Center( + child: Text( + state.diary.contentText, + overflow: TextOverflow.fade, + style: textStyle.bodyMedium, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + MoodIconComponent( + value: state.diary.mood, + width: (textStyle.titleSmall!.fontSize! * textStyle.titleSmall!.height!), + ), + if (state.diary.weather.isNotEmpty) ...[ + const SizedBox( + height: 12.0, + child: VerticalDivider( + width: 12.0, + ), + ), + Icon( + WeatherIcon.map[state.diary.weather.first], + size: textStyle.titleSmall!.fontSize! * textStyle.titleSmall!.height!, + ) + ], + const SizedBox( + width: 12.0, + ), + Text( + state.diary.time.toString().split(' ')[0], + style: textStyle.titleSmall, + ), + ], + ), + Text( + i18n.shareName, + style: textStyle.labelMedium, + ) + ], + ), + ), + ], + ), + ), + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + logic.share(); + }, + child: const Icon(Icons.share), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/start/start_logic.dart b/lib/pages/start/start_logic.dart new file mode 100644 index 0000000..9601763 --- /dev/null +++ b/lib/pages/start/start_logic.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/channel.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:uuid/uuid.dart'; + +import 'start_state.dart'; + +class StartLogic extends GetxController { + final StartState state = StartState(); + + @override + void onInit() { + // TODO: implement onInit + //如果不是首次启动,直接进入 + super.onInit(); + } + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + void toPrivacy() { + Get.toNamed(AppRoutes.privacyPage); + } + + void toAgreement() { + Get.toNamed(AppRoutes.agreementPage); + } + + Future toHome() async { + await Utils().prefUtil.setValue('firstStart', false); + if (Platform.isAndroid) { + await setUuid(); + await Utils().supabaseUtil.initSupabase(); + await Utils().updateUtil.initShiply(); + } + Get.offAllNamed(AppRoutes.homePage); + } + + Future setUuid() async { + var oaid = await OAIDChannel.getOAID(); + if (oaid != null) { + await Utils().prefUtil.setValue('uuid', md5.convert(utf8.encode(oaid)).toString()); + } else { + await Utils().prefUtil.setValue('uuid', md5.convert(utf8.encode(const Uuid().v7())).toString()); + } + } +} diff --git a/lib/pages/start/start_state.dart b/lib/pages/start/start_state.dart new file mode 100644 index 0000000..9a5a157 --- /dev/null +++ b/lib/pages/start/start_state.dart @@ -0,0 +1,5 @@ +class StartState { + StartState() { + ///Initialize variables + } +} diff --git a/lib/pages/start/start_view.dart b/lib/pages/start/start_view.dart new file mode 100644 index 0000000..2f141b1 --- /dev/null +++ b/lib/pages/start/start_view.dart @@ -0,0 +1,100 @@ +import 'dart:io'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:get/get.dart'; + +import 'start_logic.dart'; + +class StartPage extends StatelessWidget { + const StartPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + //final state = Bind.find().state; + final i18n = AppLocalizations.of(context)!; + final colorScheme = Theme.of(context).colorScheme; + final textStyle = Theme.of(context).textTheme; + return GetBuilder( + init: logic, + assignId: true, + builder: (logic) { + return Scaffold( + appBar: AppBar(), + extendBodyBehindAppBar: true, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 320, + padding: const EdgeInsets.all(10.0), + child: RichText( + text: TextSpan(children: [ + TextSpan(text: i18n.startTitle1), + TextSpan(text: i18n.startTitle2, style: TextStyle(color: colorScheme.primary)), + ], style: textStyle.titleLarge!.copyWith(fontWeight: FontWeight.bold)), + textAlign: TextAlign.center, + ), + ), + Container( + width: 320, + padding: const EdgeInsets.all(10.0), + child: Text( + i18n.startTitle3, + textAlign: TextAlign.center, + ), + ), + Container( + width: 320, + padding: const EdgeInsets.all(10.0), + child: RichText( + text: TextSpan(children: [ + TextSpan(text: i18n.welcome1), + TextSpan( + text: i18n.welcome2, + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + recognizer: TapGestureRecognizer() + ..onTap = () { + logic.toPrivacy(); + }), + TextSpan(text: i18n.welcome3), + TextSpan( + text: i18n.welcome4, + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + recognizer: TapGestureRecognizer() + ..onTap = () { + logic.toAgreement(); + }), + TextSpan(text: i18n.welcome5) + ], style: textStyle.bodyLarge), + )), + Container( + width: 320, + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FilledButton.tonal( + onPressed: () { + exit(0); + }, + child: Text(i18n.startChoice1)), + FilledButton( + onPressed: () { + logic.toHome(); + }, + child: Text(i18n.startChoice2)) + ], + ), + ) + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/user/user_logic.dart b/lib/pages/user/user_logic.dart new file mode 100644 index 0000000..d7bb46d --- /dev/null +++ b/lib/pages/user/user_logic.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/router/app_routes.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'user_state.dart'; + +class UserLogic extends GetxController { + final UserState state = UserState(); + + @override + void onReady() { + // TODO: implement onReady + super.onReady(); + } + + @override + void onClose() { + // TODO: implement onClose + super.onClose(); + } + + Future signOut() async { + await Utils().supabaseUtil.signOut(); + Get.offAndToNamed(AppRoutes.loginPage); + } +} diff --git a/lib/pages/user/user_state.dart b/lib/pages/user/user_state.dart new file mode 100644 index 0000000..93bee47 --- /dev/null +++ b/lib/pages/user/user_state.dart @@ -0,0 +1,12 @@ +import 'package:mood_diary/utils/utils.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class UserState { + late User? user = Utils().supabaseUtil.user; + + late Session? session = Utils().supabaseUtil.session; + + UserState() { + ///Initialize variables + } +} diff --git a/lib/pages/user/user_view.dart b/lib/pages/user/user_view.dart new file mode 100644 index 0000000..4213043 --- /dev/null +++ b/lib/pages/user/user_view.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'user_logic.dart'; + +class UserPage extends StatelessWidget { + const UserPage({super.key}); + + @override + Widget build(BuildContext context) { + final logic = Bind.find(); + final state = Bind.find().state; + Utils().logUtil.printInfo(state.session); + return Scaffold( + appBar: AppBar(), + body: ListView( + children: [ + FilledButton( + onPressed: () { + logic.signOut(); + }, + child: const Text('退出登录')), + ], + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart new file mode 100644 index 0000000..da9d294 --- /dev/null +++ b/lib/router/app_pages.dart @@ -0,0 +1,159 @@ +import 'package:get/get.dart'; +import 'package:mood_diary/pages/agreement/agreement_logic.dart'; +import 'package:mood_diary/pages/agreement/agreement_view.dart'; +import 'package:mood_diary/pages/analyse/analyse_logic.dart'; +import 'package:mood_diary/pages/analyse/analyse_view.dart'; +import 'package:mood_diary/pages/assistant/assistant_logic.dart'; +import 'package:mood_diary/pages/assistant/assistant_view.dart'; +import 'package:mood_diary/pages/category_manager/category_manager_logic.dart'; +import 'package:mood_diary/pages/category_manager/category_manager_view.dart'; +import 'package:mood_diary/pages/diary/diary_logic.dart'; +import 'package:mood_diary/pages/diary/diary_view.dart'; +import 'package:mood_diary/pages/draw/draw_logic.dart'; +import 'package:mood_diary/pages/draw/draw_view.dart'; +import 'package:mood_diary/pages/edit/edit_logic.dart'; +import 'package:mood_diary/pages/edit/edit_view.dart'; +import 'package:mood_diary/pages/font/font_logic.dart'; +import 'package:mood_diary/pages/font/font_view.dart'; +import 'package:mood_diary/pages/home/home_logic.dart'; +import 'package:mood_diary/pages/home/home_view.dart'; +import 'package:mood_diary/pages/image/image_logic.dart'; +import 'package:mood_diary/pages/image/image_view.dart'; +import 'package:mood_diary/pages/laboratory/laboratory_logic.dart'; +import 'package:mood_diary/pages/laboratory/laboratory_view.dart'; +import 'package:mood_diary/pages/lock/lock_logic.dart'; +import 'package:mood_diary/pages/lock/lock_view.dart'; +import 'package:mood_diary/pages/login/login_logic.dart'; +import 'package:mood_diary/pages/login/login_view.dart'; +import 'package:mood_diary/pages/privacy/privacy_logic.dart'; +import 'package:mood_diary/pages/privacy/privacy_view.dart'; +import 'package:mood_diary/pages/recycle/recycle_logic.dart'; +import 'package:mood_diary/pages/recycle/recycle_view.dart'; +import 'package:mood_diary/pages/setting/setting_logic.dart'; +import 'package:mood_diary/pages/setting/setting_view.dart'; +import 'package:mood_diary/pages/share/share_logic.dart'; +import 'package:mood_diary/pages/share/share_view.dart'; +import 'package:mood_diary/pages/start/start_logic.dart'; +import 'package:mood_diary/pages/start/start_view.dart'; +import 'package:mood_diary/pages/user/user_logic.dart'; +import 'package:mood_diary/pages/user/user_view.dart'; + +import 'app_routes.dart'; + +class AppPages { + static final List routes = [ + //启动页 + GetPage( + name: AppRoutes.startPage, + page: () => const StartPage(), + binds: [Bind.lazyPut(() => StartLogic())], + ), + //首页路由 + GetPage( + name: AppRoutes.homePage, + page: () => const HomePage(), + binds: [Bind.lazyPut(() => HomeLogic())], + ), + //分析 + GetPage( + name: AppRoutes.analysePage, + page: () => const AnalysePage(), + binds: [Bind.lazyPut(() => AnalyseLogic())], + ), + //设置 + GetPage( + name: AppRoutes.settingPage, + page: () => const SettingPage(), + binds: [Bind.lazyPut(() => SettingLogic())], + ), + //日记页路由 + GetPage( + name: AppRoutes.diaryPage, + page: () => const DiaryPage(), + binds: [Bind.lazyPut(() => DiaryLogic())], + ), + //图片路由 + GetPage( + name: AppRoutes.photoPage, + page: () => const ImagePage(), + binds: [Bind.lazyPut(() => ImageLogic())], + ), + //回收站 + GetPage( + name: AppRoutes.recyclePage, + page: () => const RecyclePage(), + binds: [Bind.lazyPut(() => RecycleLogic())], + ), + //登录路由 + GetPage( + name: AppRoutes.loginPage, + page: () => const LoginPage(), + binds: [Bind.lazyPut(() => LoginLogic())], + ), + //新增/编辑日记路由 + GetPage( + name: AppRoutes.editPage, + page: () => const EditPage(), + binds: [Bind.lazyPut(() => EditLogic())], + ), + //分享页路由 + GetPage( + name: AppRoutes.sharePage, + page: () => const SharePage(), + binds: [Bind.lazyPut(() => ShareLogic())], + ), + //字体页路由 + GetPage( + name: AppRoutes.fontPage, + page: () => const FontPage(), + binds: [Bind.lazyPut(() => FontLogic())], + ), + //实验室路由 + GetPage( + name: AppRoutes.laboratoryPage, + page: () => const LaboratoryPage(), + binds: [Bind.lazyPut(() => LaboratoryLogic())], + ), + //画画路由 + GetPage( + name: AppRoutes.drawPage, + page: () => const DrawPage(), + binds: [Bind.lazyPut(() => DrawLogic())], + ), + //助手路由 + GetPage( + name: AppRoutes.assistantPage, + page: () => const AssistantPage(), + binds: [Bind.lazyPut(() => AssistantLogic())], + ), + //隐私政策 + GetPage( + name: AppRoutes.privacyPage, + page: () => const PrivacyPage(), + binds: [Bind.lazyPut(() => PrivacyLogic())], + ), + //用户协议 + GetPage( + name: AppRoutes.agreementPage, + page: () => const AgreementPage(), + binds: [Bind.lazyPut(() => AgreementLogic())], + ), + //锁 + GetPage( + name: AppRoutes.lockPage, + page: () => const LockPage(), + binds: [Bind.lazyPut(() => LockLogic())], + ), + GetPage( + name: AppRoutes.userPage, + page: () => const UserPage(), + binds: [Bind.lazyPut(() => UserLogic())], + ), + + GetPage( + name: AppRoutes.categoryManagerPage, + page: () => const CategoryManagerPage(), + binds: [Bind.lazyPut(() => CategoryManagerLogic())], + ), + ]; +} diff --git a/lib/router/app_routes.dart b/lib/router/app_routes.dart new file mode 100644 index 0000000..0476f36 --- /dev/null +++ b/lib/router/app_routes.dart @@ -0,0 +1,60 @@ +abstract class AppRoutes { + //主页路由 + static const homePage = '/home'; + + //登录路由 + static const loginPage = '/login'; + + //日记页路由 + static const diaryPage = '/diary'; + + //分析页路由 + static const analysePage = '/analyse'; + + //设置页路由 + static const settingPage = '/setting'; + + //新增/编辑日记页路由 + static const editPage = '/edit'; + + //图片预览路由 + static const photoPage = '/photo'; + + //回收站路由 + static const recyclePage = '/recycle'; + + //图片分享页路由 + static const sharePage = '/share'; + + //字体调节页路由 + static const fontPage = '/font'; + + //实验室路由 + static const laboratoryPage = '/laboratory'; + + //画画路由 + static const drawPage = '/draw'; + + //助手路由 + static const assistantPage = '/assistant'; + + //启动页路由 + static const startPage = '/start'; + + //隐私政策 + static const privacyPage = '/privacy'; + + //用户协议 + static const agreementPage = '/agreement'; + + //锁 + static const lockPage = '/lock'; + + static const userPage = '/user'; + + //日记管理 + static const diaryManagerPage = '/diaryManager'; + + //分类管理 + static const categoryManagerPage = '/categoryManager'; +} diff --git a/lib/utils/array_util.dart b/lib/utils/array_util.dart new file mode 100644 index 0000000..c8fb777 --- /dev/null +++ b/lib/utils/array_util.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; + +class ArrayUtil { + //统计列表元素出现次数 + Map countList(List list) { + Map counts = {}; + for (var item in list) { + counts[item] = counts.containsKey(item) ? counts[item]! + 1 : 1; + } + return counts; + } + + //统计列表元素总长度 + int countListItemLength(List list) { + return list.fold(0, (sum, content) => sum + content.length); + } + + //列表去重 + List toSetList(List list) { + return list.toSet().toList(); + } + + //将 List 转换为字符串 + String listToString(List list) { + return jsonEncode(list); + } + + //将字符串转换为 List + List stringToList(String jsonString) { + return jsonDecode(jsonString).cast(); + } +} diff --git a/lib/utils/auth_util.dart b/lib/utils/auth_util.dart new file mode 100644 index 0000000..c74e8b4 --- /dev/null +++ b/lib/utils/auth_util.dart @@ -0,0 +1,35 @@ +import 'package:local_auth/local_auth.dart'; +import 'package:local_auth_android/local_auth_android.dart'; + +class AuthUtil { + late final LocalAuthentication _authentication = LocalAuthentication(); + + //生物识别 + Future check() async { + return await _authentication.authenticate( + authMessages: [ + const AndroidAuthMessages( + biometricHint: "", + biometricNotRecognized: "验证失败", + biometricSuccess: "验证成功", + cancelButton: "取消", + goToSettingsButton: "设置", + goToSettingsDescription: "请先在系统中开启指纹", + signInTitle: "扫描您的指纹以继续", + ) + ], + localizedReason: '安全验证', + options: const AuthenticationOptions( + useErrorDialogs: true, + stickyAuth: true, + sensitiveTransaction: true, + biometricOnly: true, + ), + ); + } + + //判断是否有硬件 + Future canCheckBiometrics() async { + return await _authentication.canCheckBiometrics; + } +} diff --git a/lib/utils/cache_util.dart b/lib/utils/cache_util.dart new file mode 100644 index 0000000..6c4c191 --- /dev/null +++ b/lib/utils/cache_util.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:mood_diary/utils/utils.dart'; + +class CacheUtil { + Future?> getCacheList(String key, Future?> Function() fetchData, + {int maxAgeMillis = 900000}) async { + var cachedData = Utils().prefUtil.getValue>(key); + // 检查缓存是否有效,如果无效则更新缓存 + if (cachedData == null || _isCacheExpired(cachedData, maxAgeMillis)) { + await _updateCacheList(key, fetchData); + + cachedData = Utils().prefUtil.getValue>(key); // 获取更新后的缓存数据 + } + return cachedData; + } + + bool _isCacheExpired(List cachedData, int maxAgeMillis) { + if (cachedData.length < 2) { + return true; // 缓存数据格式不正确,视为过期 + } + int timestamp = int.parse(cachedData.last); + return DateTime.now().millisecondsSinceEpoch - timestamp >= maxAgeMillis; + } + + Future _updateCacheList(String key, Future?> Function() fetchData) async { + var newData = await fetchData(); + if (newData != null) { + await Utils() + .prefUtil + .setValue>(key, newData..add(DateTime.now().millisecondsSinceEpoch.toString())); + } + } +} diff --git a/lib/utils/channel.dart b/lib/utils/channel.dart new file mode 100644 index 0000000..1ca53ae --- /dev/null +++ b/lib/utils/channel.dart @@ -0,0 +1,41 @@ +import 'package:flutter/services.dart'; + +class ViewChannel { + static const MethodChannel _channel = MethodChannel('view_channel'); + + static setSystemUIVisibility() async { + await _channel.invokeMethod('setSystemUIVisibility'); + } +} + +class ShiplyChannel { + static const MethodChannel _channel = MethodChannel('shiply_channel'); + + static initShiply() async { + await _channel.invokeMethod('initShiply'); + } + + static Future checkUpdate() async { + return await _channel.invokeMethod('checkUpdate'); + } + + static Future startDownload() async { + await _channel.invokeMethod('startDownload'); + } +} + +class FontChannel { + static const MethodChannel _channel = MethodChannel('font_channel'); + + static Future getFont() async { + return await _channel.invokeMethod('getFont'); + } +} + +class OAIDChannel { + static const MethodChannel _channel = MethodChannel('oaid_channel'); + + static Future getOAID() async { + return await _channel.invokeMethod('getOAID'); + } +} diff --git a/lib/utils/data/isar.dart b/lib/utils/data/isar.dart new file mode 100644 index 0000000..367e384 --- /dev/null +++ b/lib/utils/data/isar.dart @@ -0,0 +1,230 @@ +import 'package:isar/isar.dart'; +import 'package:mood_diary/common/models/isar/category.dart'; +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:path/path.dart'; +import 'package:uuid/uuid.dart'; + +class IsarUtil { + late final Isar _isar; + + Future initIsar() async { + _isar = await Isar.openAsync( + schemas: [ + DiarySchema, + CategorySchema, + ], + directory: Utils().fileUtil.getRealPath('database', ''), + ); + } + + Future dataMigration(String path) async { + var oldIsar = await Isar.openAsync(schemas: [DiarySchema, CategorySchema], directory: path, name: 'old'); + List oldDiaryList = await oldIsar.diarys.where().findAllAsync(); + + await _isar.writeAsync((isar) { + isar.diarys.clear(); + isar.diarys.putAll(oldDiaryList); + }); + oldIsar.close(deleteFromDisk: true); + } + + //清空数据 + Future clearIsar() async { + await _isar.writeAsync((isar) { + isar.clear(); + }); + } + + Map getSize() { + return Utils().fileUtil.bytesToUnits(_isar.diarys.getSize(includeIndexes: true)); + } + + //导出数据 + Future exportIsar(String dir, String path, String fileName) async { + var isar = Isar.open( + schemas: [DiarySchema, CategorySchema], + directory: join(dir, 'database'), + ); + isar.copyToFile(join(path, fileName)); + isar.close(); + } + + //插入一条日记 + Future insertADiary(Diary diary) async { + await _isar.writeAsync((isar) { + diary.id = const Uuid().v7(); + isar.diarys.put(diary); + }); + } + + //获取全部日历 + Future> getAllDiary(int offset, int limit) async { + return await _isar.diarys.where().showEqualTo(true).sortByTimeDesc().findAllAsync(offset: offset, limit: limit); + } + + //根据月份获取日记 + Future> getDiariesByMonth(int year, int month) async { + return await _isar.diarys + .where() + .yMEqualTo('${year.toString()}/${month.toString()}') + .showEqualTo(true) + .sortByTimeDesc() + .findAllAsync(); + } + + //根据id获取日记 + Future getDiaryByID(String id) async { + return await _isar.diarys.getAsync(id); + } + + //根据日期范围获取日记 + Future> getDiariesByDateRange(DateTime start, DateTime end) async { + return await _isar.diarys.where().timeBetween(start, end).showEqualTo(true).findAllAsync(); + } + + //获取指定范围内的天气 + Future>> getWeatherByDateRange(DateTime start, DateTime end) async { + return await _isar.diarys + .where() + .showEqualTo(true) + .timeBetween(start, end) + .distinctByYMd() + .weatherProperty() + .findAllAsync(); + } + + //获取指定范围的心情指数 + Future> getMoodByDateRange(DateTime start, DateTime end) async { + return await _isar.diarys + .where() + .showEqualTo(true) + .timeBetween(start, end) + .distinctByYMd() + .moodProperty() + .findAllAsync(); + } + + //删除某篇日记 + Future deleteADiary(String id) async { + return await _isar.writeAsync((isar) { + return isar.diarys.delete(id); + }); + } + + //回收站日记 + Future> getRecycleBinDiaries() async { + return await _isar.diarys.where().showEqualTo(false).sortByTimeDesc().findAllAsync(); + } + + //更新日记 + Future updateADiary(Diary diary) async { + await _isar.writeAsync((isar) { + isar.diarys.put(diary); + }); + } + + //查询日记 + Future> searchDiaries(String value) async { + return await _isar.diarys.where().showEqualTo(true).contentTextContains(value).findAllAsync(); + } + + Future> searchDiariesByTag(String value) async { + return await _isar.diarys.where().showEqualTo(true).tagsElementContains(value).findAllAsync(); + } + + //获取日记总数 + Future countDiaries() async { + return await _isar.diarys.where().showEqualTo(true).countAsync(); + } + + //获取分类总数 + Future countCategories() async { + return await _isar.categorys.where().countAsync(); + } + + //获取分类名称 + Future getCategoryName(String id) async { + return await _isar.categorys.getAsync(id); + } + + Future insertACategory(Category category) async { + return await _isar.writeAsync((isar) { + if (isar.categorys.where().categoryNameEqualTo(category.categoryName).isEmpty()) { + category.id = const Uuid().v7(); + isar.categorys.put(category); + return true; + } else { + return false; + } + }); + } + + Future updateACategory(Category category) async { + await _isar.writeAsync((isar) { + isar.categorys.put(category); + }); + } + + Future deleteACategory(String id) async { + return await _isar.writeAsync((isar) { + if (isar.diarys.where().categoryIdEqualTo(id).isEmpty()) { + return isar.categorys.delete(id); + } else { + return false; + } + }); + } + + // 获取所有日记内容 + Future> getContentList() async { + return await _isar.diarys.where().showEqualTo(true).contentTextProperty().findAllAsync(); + } + + //获取所有分类 + List getAllCategory() { + return _isar.categorys.where().findAll(); + } + + Future> getAllCategoryAsync() async { + return _isar.categorys.where().sortById().findAllAsync(); + } + + //获取对应分类的日记 + Future> getDiaryByCategory(String categoryId, int offset, int limit) async { + return await _isar.diarys + .where() + .showEqualTo(true) + .categoryIdEqualTo(categoryId) + .sortByTimeDesc() + .findAllAsync(offset: offset, limit: limit); + } + + //构建搜索 + void buildSearch(String dir) async { + var isar = Isar.open( + schemas: [DiarySchema, CategorySchema], + directory: dir, + ); + final countDiary = isar.diarys.where().count(); + var result = []; + for (var i = 0; i < countDiary; i += 50) { + var diaries = isar.diarys.where().findAll(offset: i, limit: 50); + for (var diary in diaries) { + //获取封面比例 + // if (diary.imageName.isNotEmpty) { + // diary.aspect = await Utils() + // .mediaUtil + // .getImageAspectRatio(FileImage(File(Utils().fileUtil.getRealPath('image', diary.imageName.first)))); + // result.add(diary); + // } + diary.contentText = diary.contentText.trim(); + result.add(diary); + } + //final categorys = isar.categorys.where().findAll(); + isar.write((isar) { + isar.diarys.putAll(result); + }); + } + } +} diff --git a/lib/utils/data/pref.dart b/lib/utils/data/pref.dart new file mode 100644 index 0000000..dff9b42 --- /dev/null +++ b/lib/utils/data/pref.dart @@ -0,0 +1,144 @@ +import 'package:mood_diary/utils/utils.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class PrefUtil { + late final SharedPreferencesWithCache _prefs; + + static const allowList = { + //应用版本 + 'appVersion', + //首次启动标识 + 'firstStart', + //自动同步 + 'autoSync', + //动态配色支持 + 'supportDynamicColor', + //主题颜色 + 'color', + //主题模式 + 'themeMode', + //动态配色 + 'dynamicColor', + //图片质量 + 'quality', + //本地化 + 'local', + //应用锁 + 'lock', + //uuid + 'uuid', + //字体缩放 + 'fontScale', + //立即锁定 + 'lockNow', + //字体样式 + 'fontTheme', + //和风key + 'qweatherKey', + 'tencentId', + 'tencentKey', + //侧边栏天气 + 'getWeather', + //天气缓存 + 'weather', + //一言缓存 + 'hitokoto', + //图片缓存 + 'bingImage', + //第一次打开的时间 + 'startTime', + //应用文档路径 + 'supportPath', + //缓存路径 + 'cachePath', + //密码 + 'password', + //生物识别支持 + 'supportBiometrics', + //自定义首页名称 + 'customTitleName', + //首页视图模式 + 'homeViewMode' + }; + + Future initPref() async { + _prefs = await SharedPreferencesWithCache.create( + cacheOptions: const SharedPreferencesWithCacheOptions(allowList: allowList)); + + // 首次启动 + var firstStart = _prefs.getBool('firstStart') ?? true; + await _prefs.setBool('firstStart', firstStart); + + // 获取当前应用版本 + var currentVersion = (await Utils().packageUtil.getPackageInfo()).version; + var appVersion = _prefs.getString('appVersion'); + + // 如果是首次启动或版本不一致 + if (firstStart || appVersion == null || appVersion != currentVersion) { + await _prefs.setString('appVersion', currentVersion); + await setDefaultValues(); + } + } + + // 设置默认值的方法 + Future setDefaultValues() async { + await _prefs.setBool('autoSync', _prefs.getBool('autoSync') ?? false); + await _prefs.setBool( + 'supportBiometrics', _prefs.getBool('supportBiometrics') ?? await Utils().authUtil.canCheckBiometrics()); + await _prefs.setBool( + 'supportDynamicColor', _prefs.getBool('supportDynamicColor') ?? await Utils().themeUtil.supportDynamicColor()); + await _prefs.setInt('color', _prefs.getInt('color') ?? (await Utils().themeUtil.supportDynamicColor() ? -1 : 0)); + await _prefs.setInt('themeMode', _prefs.getInt('themeMode') ?? 0); + await _prefs.setBool('dynamicColor', _prefs.getBool('dynamicColor') ?? true); + await _prefs.setInt('quality', _prefs.getInt('quality') ?? 2); + await _prefs.setBool('local', _prefs.getBool('local') ?? false); + await _prefs.setBool('lock', _prefs.getBool('lock') ?? false); + await _prefs.setDouble('fontScale', _prefs.getDouble('fontScale') ?? 1.0); + await _prefs.setBool('lockNow', _prefs.getBool('lockNow') ?? false); + await _prefs.setInt('fontTheme', _prefs.getInt('fontTheme') ?? 0); + await _prefs.setString( + 'supportPath', _prefs.getString('supportPath') ?? (await getApplicationSupportDirectory()).path); + await _prefs.setString('cachePath', _prefs.getString('cachePath') ?? (await getApplicationCacheDirectory()).path); + await _prefs.setBool('getWeather', _prefs.getBool('getWeather') ?? false); + await _prefs.setInt('startTime', _prefs.getInt('startTime') ?? DateTime.now().millisecondsSinceEpoch); + await _prefs.setString('customTitleName', _prefs.getString('customTitleName') ?? ''); + await _prefs.setString('homeViewMode', _prefs.getString('homeViewMode') ?? 'list'); + } + + Future setValue(String key, T value) async { + if (T == int) { + await _prefs.setInt(key, value as int); + } else if (T == bool) { + await _prefs.setBool(key, value as bool); + } else if (T == double) { + await _prefs.setDouble(key, value as double); + } else if (T == String) { + await _prefs.setString(key, value as String); + } else if (T == List) { + await _prefs.setStringList(key, value as List); + } else { + throw ArgumentError('Unsupported type: $T'); + } + } + + T? getValue(String key) { + if (T == int) { + return _prefs.getInt(key) as T?; + } else if (T == bool) { + return _prefs.getBool(key) as T?; + } else if (T == double) { + return _prefs.getDouble(key) as T?; + } else if (T == String) { + return _prefs.getString(key) as T?; + } else if (T == List) { + return _prefs.getStringList(key) as T?; + } else { + throw ArgumentError('Unsupported type: $T'); + } + } + + Future removeValue(String key) async { + await _prefs.remove(key); + } +} diff --git a/lib/utils/data/supabase.dart b/lib/utils/data/supabase.dart new file mode 100644 index 0000000..67e7585 --- /dev/null +++ b/lib/utils/data/supabase.dart @@ -0,0 +1,52 @@ +import 'package:mood_diary/common/models/isar/diary.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class SupabaseUtil { + late final SupabaseClient _supabase = Supabase.instance.client; + + Session? get session => _supabase.auth.currentSession; + + User? get user => _supabase.auth.currentUser; + + Future initSupabase() async { + if (Utils().prefUtil.getValue('firstStart') == false) { + await Supabase.initialize( + url: '', + anonKey: '', + ); + } + } + + //注册 + Future signUp(String email, String password) async { + await _supabase.auth.signUp( + email: email, + password: password, + ); + await _supabase.from('test').select('*'); + } + + Future uploadDiary(List diaryList) async { + var u = _supabase.auth.currentUser; + //User不为空说明已经登录 + if (u != null) { + //插入数据库时加一个UserID字段作为唯一标识 + await _supabase.from('diary').insert(List.generate(diaryList.length, (index) {})); + } + } + + Future>> getData() async { + return await _supabase.from('diary').select('*'); + } + + //登录 + Future signIn(String email, String password) async { + await _supabase.auth.signInWithPassword(email: email, password: password); + } + + //退出登录 + Future signOut() async { + await _supabase.auth.signOut(); + } +} diff --git a/lib/utils/file_util.dart b/lib/utils/file_util.dart new file mode 100644 index 0000000..5dcdb7c --- /dev/null +++ b/lib/utils/file_util.dart @@ -0,0 +1,139 @@ +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; + +class FileUtil { + late final _filePath = Utils().prefUtil.getValue('supportPath')!; + + late final _cachePath = Utils().prefUtil.getValue('cachePath')!; + + // 删除文件 + Future deleteFile(String path) async { + File file = File(path); + if (await file.exists()) { + await file.delete(); + return true; // 返回删除成功 + } else { + // 文件不存在,返回失败 + return false; + } + } + + //删除指定文件夹下的文件 + Future deleteDir(String path) async { + Directory directory = Directory(path); + if (await directory.exists()) { + await directory.delete(recursive: true); + } + } + + Future initCreateDir() async { + if (Utils().prefUtil.getValue('firstStart') == true) { + //创建数据库文件目录 + await createDir(join(_filePath, 'database')); + await createDir(join(_filePath, 'image')); + await createDir(join(_filePath, 'audio')); + } + } + + Future createDir(String path) async { + Directory directory = Directory(path); + await directory.create(recursive: true); + } + + //计算指定路径下文件的大小 + Future> countSize() async { + var cacheDir = await getApplicationCacheDirectory(); + var bytes = 0; + var fileList = cacheDir.listSync(recursive: true); + // 计算文件总大小(字节) + for (var file in fileList) { + if (file is File) { + bytes += file.lengthSync(); + } + } + // 根据文件总大小选择合适的单位 + return bytesToUnits(bytes); + } + + Map bytesToUnits(int bytes) { + // 根据文件总大小选择合适的单位 + if (bytes < 1024) { + return {'size': bytes.toString(), 'unit': 'B', 'bytes': bytes}; + } else if (bytes < 1024 * 1024) { + return {'size': (bytes / 1024).toStringAsFixed(2), 'unit': 'KB', 'bytes': bytes}; + } else if (bytes < 1024 * 1024 * 1024) { + return {'size': (bytes / (1024 * 1024)).toStringAsFixed(2), 'unit': 'MB', 'bytes': bytes}; + } else { + return {'size': (bytes / (1024 * 1024 * 1024)).toStringAsFixed(2), 'unit': 'GB', 'bytes': bytes}; + } + } + + Future clearCache() async { + var cacheDir = await getApplicationCacheDirectory(); + if (await cacheDir.exists()) { + await cacheDir.delete(recursive: true); + } + } + + //文件导出 + Future zipFile(Map params) async { + var zipPath = params['zipPath'] as String; + var dataPath = params['dataPath'] as String; + var zipEncoder = ZipFileEncoder(); + var datetime = DateTime.now(); + var fileName = join(zipPath, '心绪日记${datetime.toString().split(' ')[0]}备份.zip'); + final outputStream = OutputFileStream(fileName); + zipEncoder.createWithBuffer(outputStream); + //备份照片 + await zipEncoder.addDirectory(Directory(join(dataPath, 'image'))); + //备份音频 + await zipEncoder.addDirectory(Directory(join(dataPath, 'audio'))); + //备份数据库 + await Utils().isarUtil.exportIsar(dataPath, zipPath, '${datetime.millisecondsSinceEpoch}.isar'); + await zipEncoder.addFile(File(join(zipPath, '${datetime.millisecondsSinceEpoch}.isar'))); + await zipEncoder.close(); + return fileName; + } + + //导入文件 + Future extractFile(String path) async { + final inputStream = InputFileStream(path); + //删除图片文件夹 + await deleteDir(join(_filePath, 'image')); + //删除音频文件夹 + await deleteDir(join(_filePath, 'audio')); + //重新创建图片文件夹 + await createDir(join(_filePath, 'image')); + //重新创建音频文件夹 + await createDir(join(_filePath, 'audio')); + var archive = ZipDecoder().decodeBuffer(inputStream); + for (var file in archive.files) { + //如果是数据库 + if (file.name.endsWith('.isar')) { + final outputStream = OutputFileStream(join(_cachePath, 'old.isar')); + file.writeContent(outputStream); + } else { + //图片 + final outputStream = OutputFileStream(join(_filePath, file.name)); + file.writeContent(outputStream); + } + } + //复制数据库 + await Utils().isarUtil.dataMigration(_cachePath); + } + + String getRealPath(String fileType, String fileName) { + return join(_filePath, fileType, fileName); + } + + String getCachePath(String fileName) { + if (Platform.isWindows) { + return 'C:/temp/$fileName'; + } + return join(_cachePath, fileName); + } +} diff --git a/lib/utils/function_extensions.dart b/lib/utils/function_extensions.dart new file mode 100644 index 0000000..f05c7aa --- /dev/null +++ b/lib/utils/function_extensions.dart @@ -0,0 +1,66 @@ +import 'dart:async'; +import 'dart:ui'; + +// Function extensions to add throttling and debouncing capabilities +extension FunctionExtensions on Function { + VoidCallback throttle() { + return FunctionProxy(this).throttle; + } + + VoidCallback throttleWithTimeout({int? timeout}) { + return FunctionProxy(this, timeout: timeout).throttleWithTimeout; + } + + VoidCallback debounce({int? timeout}) { + return FunctionProxy(this, timeout: timeout).debounce; + } +} + +// FunctionProxy class to implement throttling and debouncing +class FunctionProxy { + static final Map _throttleMap = {}; + static final Map _debounceMap = {}; + final Function target; + final int timeout; + + FunctionProxy(this.target, {int? timeout}) : timeout = timeout ?? 500; + + // Throttle function to limit the execution rate + void throttle() async { + int key = target.hashCode; + bool canExecute = _throttleMap[key] ?? true; + if (canExecute) { + _throttleMap[key] = false; + try { + await target(); + } catch (e) { + rethrow; + } finally { + _throttleMap.remove(key); + } + } + } + + // Throttle function with a specified timeout + void throttleWithTimeout() { + int key = target.hashCode; + bool canExecute = _throttleMap[key] ?? true; + if (canExecute) { + _throttleMap[key] = false; + Timer(Duration(milliseconds: timeout), () { + _throttleMap.remove(key); + }); + target(); + } + } + + // Debounce function to delay execution until after a specified period + void debounce() { + int key = target.hashCode; + _debounceMap[key]?.cancel(); + _debounceMap[key] = Timer(Duration(milliseconds: timeout), () { + _debounceMap.remove(key); + target(); + }); + } +} diff --git a/lib/utils/http_util.dart b/lib/utils/http_util.dart new file mode 100644 index 0000000..4e64dd3 --- /dev/null +++ b/lib/utils/http_util.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; + +class HttpUtil { + late final Dio _dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 3))); + + HttpUtil() { + _dio.interceptors.add(InterceptorsWrapper( + onRequest: (RequestOptions options, RequestInterceptorHandler handler) { + return handler.next(options); + }, + onResponse: (Response response, ResponseInterceptorHandler handler) { + return handler.next(response); + }, + onError: (DioException error, ErrorInterceptorHandler handler) { + return handler.next(error); + }, + )); + } + + Future> get(String path, {Map? parameters, ResponseType? type}) async { + return await _dio.get(path, queryParameters: parameters, options: Options(responseType: type)); + } + + Future> post(String path, {Map? header, data, Options? option}) async { + return await _dio.post(path, options: Options(headers: header), data: data); + } + + Future?> postStream(String path, {Map? header, Object? data}) async { + Response response = + await _dio.post(path, options: Options(responseType: ResponseType.stream, headers: header), data: data); + StreamTransformer> transformer = StreamTransformer.fromHandlers(handleData: (data, sink) { + sink.add(List.from(data)); + }); + return response.data?.stream.transform(transformer).transform(const Utf8Decoder()).transform(const LineSplitter()); + } +} diff --git a/lib/utils/layout_util.dart b/lib/utils/layout_util.dart new file mode 100644 index 0000000..e45a98c --- /dev/null +++ b/lib/utils/layout_util.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/values/size.dart'; + +class LayoutUtil { + //获取设备类型 + ScreenSize getSize(deviceWidth) { + if (deviceWidth > 900) return ScreenSize.desktop; + if (deviceWidth > 600) return ScreenSize.tablet; + if (deviceWidth > 300) return ScreenSize.handset; + return ScreenSize.watch; + } + + //获取方向 + bool isLandSpace() { + return MediaQuery.sizeOf(Get.context!).aspectRatio >= 1.0; + } +} diff --git a/lib/utils/log_util.dart b/lib/utils/log_util.dart new file mode 100644 index 0000000..0ef49b6 --- /dev/null +++ b/lib/utils/log_util.dart @@ -0,0 +1,30 @@ +import 'package:flutter/foundation.dart'; +import 'package:logger/logger.dart'; + +class LogUtil { + late Logger logger; + + LogUtil() { + if (kDebugMode) { + logger = Logger(); + } + } + + void printError(message, {required Object error, StackTrace? stackTrace}) { + if (kDebugMode) { + logger.e(message, error: error, stackTrace: stackTrace); + } + } + + void printWTF(message, {required Object error, StackTrace? stackTrace}) { + if (kDebugMode) { + logger.f(message, error: error, stackTrace: stackTrace); + } + } + + void printInfo(message) { + if (kDebugMode) { + logger.i(message); + } + } +} diff --git a/lib/utils/media_util.dart b/lib/utils/media_util.dart new file mode 100644 index 0000000..24e2475 --- /dev/null +++ b/lib/utils/media_util.dart @@ -0,0 +1,102 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_image_compress/flutter_image_compress.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class MediaUtil { + late final _picker = ImagePicker(); + + //保存图片 + Future saveImages(Map imageMap) async { + await Future.forEach(imageMap.entries, (entry) async { + final file = File(Utils().fileUtil.getRealPath('image', entry.key)); + await file.writeAsBytes(entry.value, flush: true); + }); + } + + //保存录音 + Future savaAudio(List audioName) async { + await Future.forEach(audioName, (name) async { + final file = File(Utils().fileUtil.getCachePath(name)); + await file.copy(Utils().fileUtil.getRealPath('audio', name)); + }); + } + + //获取图片宽高 + Future getImageSize(ImageProvider imageProvider) async { + final Completer completer = Completer(); + final ImageStream stream = imageProvider.resolve(const ImageConfiguration()); + stream.addListener( + ImageStreamListener( + (ImageInfo info, bool _) { + final Size size = Size(info.image.width.toDouble(), info.image.height.toDouble()); + completer.complete(size); + }, + ), + ); + return completer.future; + } + + //获取图片宽高比例 + Future getImageAspectRatio(ImageProvider imageProvider) async { + final Completer completer = Completer(); + final ImageStream stream = imageProvider.resolve(const ImageConfiguration()); + stream.addListener( + ImageStreamListener( + (ImageInfo info, bool _) { + final double aspectRatio = + double.parse((info.image.width.toDouble() / info.image.height.toDouble()).toStringAsPrecision(2)); + completer.complete(aspectRatio); + }, + ), + ); + return completer.future; + } + + //获取单个图片,拍照或者相册 + Future pickPhoto(ImageSource imageSource) async { + //await callPhotoPicker(); + return await _picker.pickImage( + source: imageSource, + ); + } + + //获取单个视频 + Future pickVideo(ImageSource imageSource) async { + return await _picker.pickVideo(source: imageSource); + } + + //异步获取图片颜色 + Future getColorScheme(ImageProvider imageProvider) async { + return (await ColorScheme.fromImageProvider(provider: imageProvider)).primary; + } + + //获取多个图片 + Future> pickMultiPhoto(int limit) async { + //await callPhotoPicker(); + return await _picker.pickMultiImage(limit: limit); + } + + //图片压缩 + Future compressImage(oldImage) async { + if (Platform.isWindows) { + return oldImage; + } + var height = switch (Utils().prefUtil.getValue('quality')) { + 0 => 720, + 1 => 1080, + 2 => 1440, + _ => 1080, + }; + return await FlutterImageCompress.compressWithList( + oldImage, + minHeight: height, + minWidth: height, + format: CompressFormat.webp, + ); + } +} diff --git a/lib/utils/notice_util.dart b/lib/utils/notice_util.dart new file mode 100644 index 0000000..ab2f97e --- /dev/null +++ b/lib/utils/notice_util.dart @@ -0,0 +1,59 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:get/get.dart'; + +class NoticeUtil { + void showToast(String message) async { + final colorScheme = Theme.of(Get.context!).colorScheme; + + if (Platform.isWindows) { + ScaffoldMessenger.of(Get.context!).clearSnackBars(); + ScaffoldMessenger.of(Get.context!).showSnackBar(SnackBar( + content: Text( + message, + style: TextStyle(color: colorScheme.onSecondaryContainer), + ), + backgroundColor: colorScheme.secondaryContainer, + behavior: SnackBarBehavior.floating, + )); + } else { + Fluttertoast.cancel(); + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.SNACKBAR, + timeInSecForIosWeb: 1, + backgroundColor: colorScheme.secondaryContainer, + textColor: colorScheme.onSecondaryContainer, + fontSize: 16.0); + } + } + + void showBug(message, {required Object error, StackTrace? stackTrace}) { + final colorScheme = Theme.of(Get.context!).colorScheme; + ScaffoldMessenger.of(Get.context!).showSnackBar( + SnackBar( + content: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '出错了!请截图联系开发者。$message', + style: TextStyle(color: colorScheme.onErrorContainer), + ), + Text( + error.toString(), + style: TextStyle(color: colorScheme.onErrorContainer), + ), + Text(stackTrace?.toString() ?? '', style: TextStyle(color: colorScheme.onErrorContainer)), + ], + ), + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 20), + backgroundColor: colorScheme.errorContainer, + showCloseIcon: true, + ), + ); + } +} diff --git a/lib/utils/package_util.dart b/lib/utils/package_util.dart new file mode 100644 index 0000000..76f5ced --- /dev/null +++ b/lib/utils/package_util.dart @@ -0,0 +1,14 @@ +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class PackageUtil { + //获取版本信息 + Future getPackageInfo() async { + return await PackageInfo.fromPlatform(); + } + + Future getInfo() async { + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + return await deviceInfoPlugin.deviceInfo; + } +} diff --git a/lib/utils/permission_util.dart b/lib/utils/permission_util.dart new file mode 100644 index 0000000..b7cc651 --- /dev/null +++ b/lib/utils/permission_util.dart @@ -0,0 +1,40 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:mood_diary/utils/utils.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class PermissionUtil { + //权限申请 + Future checkPermission(Permission permission) async { + //检查当前权限 + final status = await permission.status; + //如果还没有授权或者拒绝过 + if (status.isDenied) { + //尝试申请权限 + final permissionStatus = await permission.request(); + if (permissionStatus.isDenied || permissionStatus.isPermanentlyDenied) { + Utils().noticeUtil.showToast('请授予相关权限'); + return false; + } else { + return true; + } + } else if (status.isPermanentlyDenied) { + Utils().noticeUtil.showToast('相关权限被禁用,请去设置中手动开启'); + Future.delayed(const Duration(seconds: 2), () => openAppSettings()); + return false; + } else { + return true; + } + } + + Future getLocation() async { + if (await checkPermission(Permission.location)) { + var position = await Geolocator.getLastKnownPosition(forceAndroidLocationManager: true); + if (position != null) { + return position; + } else { + return await Geolocator.getCurrentPosition(locationSettings: AndroidSettings(forceLocationManager: true)); + } + } + return null; + } +} diff --git a/lib/utils/signature_util.dart b/lib/utils/signature_util.dart new file mode 100644 index 0000000..8bf5474 --- /dev/null +++ b/lib/utils/signature_util.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class SignatureUtil { + String hex(List bytes) { + StringBuffer buffer = StringBuffer(); + for (int part in bytes) { + buffer.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}'); + } + return buffer.toString(); + } + + String sha256HexToLowercase(String input) { + return sha256.convert(utf8.encode(input)).toString().toLowerCase(); + } + + List hmacSha256(List key, List data) { + return Hmac(sha256, key).convert(data).bytes; + } + + //生成腾讯云签名 + String generateSignature(String id, String key, int timestamp, body) { + String dateTime = DateTime.fromMillisecondsSinceEpoch(timestamp, isUtc: true).toString().split(' ')[0]; + //拼接规范请求串 + var canonicalRequest = + 'POST\n/\n\ncontent-type:application/json\nhost:hunyuan.tencentcloudapi.com\nx-tc-action:chatcompletions\n\ncontent-type;host;x-tc-action\n${sha256HexToLowercase(jsonEncode(body))}'; + //待签名字符串 + var stringToSign = + 'TC3-HMAC-SHA256\n${timestamp ~/ 1000}\n$dateTime/hunyuan/tc3_request\n${sha256HexToLowercase(canonicalRequest)}'; + var date = hmacSha256(utf8.encode('TC3$key'), utf8.encode(dateTime)); + var service = hmacSha256(date, utf8.encode('hunyuan')); + var signing = hmacSha256(service, utf8.encode('tc3_request')); + var signature = hmacSha256(signing, utf8.encode(stringToSign)); + var authorization = + 'TC3-HMAC-SHA256 Credential=$id/$dateTime/hunyuan/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=${hex(signature).toLowerCase()}'; + return authorization; + } + + Map? checkTencent() { + var id = Utils().prefUtil.getValue('tencentId'); + var key = Utils().prefUtil.getValue('tencentKey'); + if (id == null || key == null) { + Utils().noticeUtil.showToast('请先配置Key'); + return null; + } else { + return {'id': id, 'key': key}; + } + } +} diff --git a/lib/utils/theme_util.dart b/lib/utils/theme_util.dart new file mode 100644 index 0000000..2aaf9d0 --- /dev/null +++ b/lib/utils/theme_util.dart @@ -0,0 +1,37 @@ +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; +import 'package:mood_diary/common/values/colors.dart'; +import 'package:mood_diary/utils/utils.dart'; + +class ThemeUtil { + Future supportDynamicColor() async { + return (await DynamicColorPlugin.getCorePalette()) != null; + } + + Future getDynamicColor() async { + return Color((await DynamicColorPlugin.getCorePalette())!.primary.get(40)); + } + + Future buildTheme(Brightness brightness) async { + final color = Utils().prefUtil.getValue('color'); + var seedColor = switch (color) { + 0 => AppColor.colorList[0], + 1 => AppColor.colorList[1], + 2 => AppColor.colorList[2], + 3 => AppColor.colorList[3], + 4 => AppColor.colorList[4], + //-1为系统配色,如果选了-1,肯定有 + _ => await getDynamicColor() + }; + + var themeData = ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: seedColor, + brightness: brightness, + dynamicSchemeVariant: color == 0 ? DynamicSchemeVariant.fidelity : DynamicSchemeVariant.tonalSpot, + ), + ); + + return themeData; + } +} diff --git a/lib/utils/update_util.dart b/lib/utils/update_util.dart new file mode 100644 index 0000000..27ae24e --- /dev/null +++ b/lib/utils/update_util.dart @@ -0,0 +1,73 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:mood_diary/common/models/shiply.dart'; +import 'package:mood_diary/utils/utils.dart'; + +import 'channel.dart'; + +class UpdateUtil { + Future initShiply() async { + //如果不是首次启动,就初始化 + if (Utils().prefUtil.getValue('firstStart') == false) { + //初始化shiply + await ShiplyChannel.initShiply(); + } + } + + Future checkUpdate() async { + var res = await ShiplyChannel.checkUpdate(); + if (res != null) { + return ShiplyResponse.fromJson(jsonDecode(res)); + } else { + return null; + } + } + + TextSpan buildReleaseNote(String version, List fix, List add) { + // 创建一个文本段落列表,用于存放每个部分的文本段 + List children = []; + final textStyle = Theme.of(Get.context!).textTheme; + // 添加版本信息 + children.add(TextSpan( + text: '$version\n', + style: textStyle.titleSmall!.copyWith( + fontWeight: FontWeight.bold, + ), + )); + + // 添加新增内容 + if (add.isNotEmpty) { + children.add(TextSpan( + text: '新增:\n', + style: textStyle.titleSmall, + )); + // 遍历新增内容列表,将每个新增项目添加到文本段落中 + for (String item in add) { + children.add(TextSpan( + text: '• $item\n', + style: textStyle.bodySmall, + )); + } + } + + // 添加修复内容 + if (fix.isNotEmpty) { + children.add(TextSpan( + text: '修复:\n', + style: textStyle.titleSmall, + )); + // 遍历修复内容列表,将每个修复项目添加到文本段落中 + for (String item in fix) { + children.add(TextSpan( + text: '• $item\n', + style: textStyle.bodySmall, + )); + } + } + children.add(const TextSpan(text: '\n')); + // 返回包含所有文本段的 TextSpan 对象 + return TextSpan(children: children); + } +} diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart new file mode 100644 index 0000000..da39ace --- /dev/null +++ b/lib/utils/utils.dart @@ -0,0 +1,60 @@ +import 'package:mood_diary/utils/auth_util.dart'; +import 'package:mood_diary/utils/cache_util.dart'; +import 'package:mood_diary/utils/data/isar.dart'; +import 'package:mood_diary/utils/data/pref.dart'; +import 'package:mood_diary/utils/data/supabase.dart'; +import 'package:mood_diary/utils/file_util.dart'; +import 'package:mood_diary/utils/http_util.dart'; +import 'package:mood_diary/utils/layout_util.dart'; +import 'package:mood_diary/utils/log_util.dart'; +import 'package:mood_diary/utils/media_util.dart'; +import 'package:mood_diary/utils/notice_util.dart'; +import 'package:mood_diary/utils/package_util.dart'; +import 'package:mood_diary/utils/permission_util.dart'; +import 'package:mood_diary/utils/signature_util.dart'; +import 'package:mood_diary/utils/theme_util.dart'; +import 'package:mood_diary/utils/update_util.dart'; + +import 'array_util.dart'; + +class Utils { + Utils._(); + + static final Utils _instance = Utils._(); + + factory Utils() => _instance; + + late final ArrayUtil arrayUtil = ArrayUtil(); + + late final AuthUtil authUtil = AuthUtil(); + + late final CacheUtil cacheUtil = CacheUtil(); + + late final FileUtil fileUtil = FileUtil(); + + late final HttpUtil httpUtil = HttpUtil(); + + late final LogUtil logUtil = LogUtil(); + + late final LayoutUtil layoutUtil = LayoutUtil(); + + late final MediaUtil mediaUtil = MediaUtil(); + + late final NoticeUtil noticeUtil = NoticeUtil(); + + late final PackageUtil packageUtil = PackageUtil(); + + late final PermissionUtil permissionUtil = PermissionUtil(); + + late final SignatureUtil signatureUtil = SignatureUtil(); + + late final ThemeUtil themeUtil = ThemeUtil(); + + late final UpdateUtil updateUtil = UpdateUtil(); + + late final IsarUtil isarUtil = IsarUtil(); + + late final PrefUtil prefUtil = PrefUtil(); + + late final SupabaseUtil supabaseUtil = SupabaseUtil(); +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..6bd09a8 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1884 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + url: "https://pub.flutter-io.cn" + source: hosted + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.8.0" + app_links: + dependency: transitive + description: + name: app_links + sha256: f04c3ca96426baba784c736a201926bd4145524c36a1b38942a351b033305e21 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.2.1" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + archive: + dependency: "direct main" + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.6.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.11.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: "752039d6aa752597c98ec212e9759519061759e402e7da59a511f39d43aa07d2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.0" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: db8fc420dadf80da18e2286c18e746fb4c3b2c5adbf0c963299dde046828886d + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.12" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.9.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" + calendar_date_picker2: + dependency: "direct main" + description: + name: calendar_date_picker2 + sha256: c7684d1a6bae19dc78c0b59101b272b47d78713d4aa0fcca55e55cf2dde9e3e4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.5" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.19.0" + console: + dependency: transitive + description: + name: console + sha256: e04e7824384c5b39389acdd6dc7d33f3efe6b232f6f16d7626f194f6a01ad69a + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.4+2" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.5" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.8" + dart_quill_delta: + dependency: transitive + description: + name: dart_quill_delta + sha256: "3452ab96321147bb567fe74bba09f1aae8a6832c00a5e3522f8bdb845bac89d8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.4.4" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.6" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.1.2" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.1" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.1" + dio: + dependency: "direct main" + description: + name: dio + sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.6.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + duration: + dependency: "direct main" + description: + name: duration + sha256: "13e5d20723c9c1dde8fb318cf86716d10ce294734e81e44ae1a817f3ae714501" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.3" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.1.2" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.3+2" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.68.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.4.1" + flutter_colorpicker: + dependency: "direct main" + description: + name: flutter_colorpicker + sha256: "969de5f6f9e2a570ac660fb7b501551451ea2a1ab9e2097e89475f60e07816ea" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + flutter_displaymode: + dependency: "direct main" + description: + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.0" + flutter_drawing_board: + dependency: "direct main" + description: + name: flutter_drawing_board + sha256: c60a75093c0d76712c6881c3f980685d4d337b0d95b8c428502816e7e0a876ec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.2" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "45a3071868092a61b11044c70422b04d39d4d9f2ef536f3c5b11fb65a1e7dd90" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: "7f79bc6c8a363063620b4e372fa86bc691e1cb28e58048cd38e030692fbd99ee" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "26df6385512e92b3789dc76b613b54b55c457a7f1532e59078b04bf189782d47" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: f02fe352b17f82b72f481de45add240db062a2585850bea1667e82cc4cd6c311 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.4+1" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.21" + flutter_quill: + dependency: "direct main" + description: + name: flutter_quill + sha256: "9b226cdec403795a29e5ef9a48b591e602ddc23a48e18d8cc35c1b8727f69376" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.4.2" + flutter_quill_delta_from_html: + dependency: transitive + description: + name: flutter_quill_delta_from_html + sha256: "5461df689818d34237f7a5109763e1e4663b973c2a44a46c398bbd46381d297a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.2.8" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: e63f49cd3b41727f47b3bde284a11a4ac62839e0604f64077d4257487510e484 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "0ec58b731776bc43097fcf751f79681b6a8f6d3bc737c94779fe9f1ad73c1a81" + url: "https://pub.flutter-io.cn" + source: hosted + version: "13.0.1" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.6.1" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.7" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.4" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.1" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.3" + get: + dependency: "direct main" + description: + name: get + sha256: ee724cdebee3a44524c4f8529370f3d661ca3c6df584ef3200bf32d272049bff + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0-release-candidate-9" + get_it: + dependency: transitive + description: + name: get_it + sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.7.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: "8703db795511f69194fe77125a0c838bbb6befc2f95717b6e40331784a8bdecb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.8.4" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + highlight: + dependency: transitive + description: + name: highlight + sha256: "5353a83ffe3e3eca7df0abfb72dcf3fa66cc56b953728e7113ad4ad88497cf21" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.4" + http: + dependency: transitive + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "40f592dd352890c3b60fec1b68e786cefb9603e05ff303dbc4dda49b304ecdf4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.0" + image: + dependency: transitive + description: + name: image + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.0" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.12+13" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.5" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.12" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.10.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1+1" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + isar: + dependency: "direct main" + description: + name: isar + sha256: ebf74d87c400bd9f7da14acb31932b50c2407edbbd40930da3a6c2a8143f85a8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0-dev.14" + isar_flutter_libs: + dependency: "direct main" + description: + name: isar_flutter_libs + sha256: "04a3f4035e213ddb6e78d0132a7c80296a085c2088c2a761b4a42ee5add36983" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0-dev.14" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.9.0" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: "direct main" + description: + name: local_auth_android + sha256: e9a3c321e94359a552b1bdd0f98f79885f2b3e27234d270f9bef5cd82b29340c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.44" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "7ba5738c874ca2b910d72385d00d2bebad9d4e807612936cf5e32bc01a048c71" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.0" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.10" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.11" + logger: + dependency: "direct main" + description: + name: logger + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2-main.4" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.2.2" + markdown_widget: + dependency: "direct main" + description: + name: markdown_widget + sha256: "216dced98962d7699a265344624bc280489d739654585ee881c95563a3252fac" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2+6" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + msix: + dependency: "direct dev" + description: + name: msix + sha256: c50d6bd1aafe0d071a3c1e5a5ccb056404502935cb0a549e3178c4aae16caf33 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.16.8" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + url: "https://pub.flutter-io.cn" + source: hosted + version: "8.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + path: + dependency: "direct main" + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.10" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" + url: "https://pub.flutter-io.cn" + source: hosted + version: "11.3.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" + url: "https://pub.flutter-io.cn" + source: hosted + version: "12.0.12" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3+2" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.2" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.0.2" + photo_view: + dependency: "direct main" + description: + name: photo_view + sha256: "1fc3d970a91295fbd1364296575f854c9863f225505c28c46e0a03e48960c75e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.15.0" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.1" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: c4197238601c7c3103b03a4bb77f2050b17d0064bf8b968309421abdebbb7f0e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + quiver: + dependency: transitive + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.1" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: d897a65ee3b1b5ddc1cf606f0b83792262d38fd5679c2df7e38da29c977513da + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + record: + dependency: "direct main" + description: + name: record + sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.2" + record_android: + dependency: transitive + description: + name: record_android + sha256: "233b12f9be9de115ea1192df645a07aaacec42a6141154e3a34cfb6d9bcc5ae7" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.5" + record_darwin: + dependency: transitive + description: + name: record_darwin + sha256: b038c26d1066eb81f4e7433bfb85f0d450ca3fac0002a7216b83a21b775ecf21 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.2" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: "4f4c0536fa75951003999701edbb717b2d3e83d912f5c83101561f87d9350d39" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.2" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.28.0" + scroll_to_index: + dependency: transitive + description: + name: scroll_to_index + sha256: b707546e7500d9f070d63e5acf74fd437ec7eeeb68d3412ef7b0afada0b4f176 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: "468c43f285207c84bcabf5737f33b914ceb8eb38398b91e5e3ad1698d1b72a52" + url: "https://pub.flutter-io.cn" + source: hosted + version: "10.0.2" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: "6ababf341050edff57da8b6990f11f4e99eaba837865e2e6defe16d039619db5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.4.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.4+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.11.1" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "28c147c805304dbc2b762becd1fc26ee0cb621ace3732b9ae61ef979aab8b367" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0" + supabase: + dependency: transitive + description: + name: supabase + sha256: "4ed1cf3298f39865c05b2d8557f92eb131a9b9af70e32e218672a0afce01a6bc" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.0" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + sha256: ff6ba3048fd47d831fdc0027d3efb99346d99b95becfcb406562454bd9b229c5 + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.7.3" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.0" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.10" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.3.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.2.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.4.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.9.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "28b1d818f2beeb108a1ed7c58a40becae86735922a98c81c7052cb278b89ea65" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.7.0" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.6.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "6dcdd298136523eaf7dfc31abaf0dfba9aa8a8dbc96670e87e9d42b6f2caf774" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.3.2" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.0+2" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + url: "https://pub.flutter-io.cn" + source: hosted + version: "14.2.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + web: + dependency: "direct overridden" + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.5.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.2" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: "47ed3900e6b0e4dfe378811a4402e85b7fc126a7daa94f840fef65ea9c8e46f4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..0436be0 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,104 @@ +name: mood_diary +description: "A new Flutter project." +# 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: 2.4.1+41 + +environment: + sdk: '>=3.4.0 <4.0.0' + +# 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 + get: 5.0.0-release-candidate-9 + dio: 5.6.0 + path: 1.9.0 + path_provider: 2.1.4 + calendar_date_picker2: 1.1.5 + logger: 2.4.0 + flutter_drawing_board: 0.9.2 + flutter_displaymode: 0.6.0 + fl_chart: 0.68.0 + file_picker: 8.1.2 + local_auth: 2.3.0 + local_auth_android: 1.0.44 + permission_handler: 11.3.1 + image_picker: 1.1.2 + device_info_plus: 10.1.2 + flutter_image_compress: 2.3.0 + photo_view: 0.15.0 + package_info_plus: 8.0.2 + uuid: 4.4.2 + flutter_quill: 10.4.2 + share_plus: 10.0.2 + url_launcher: 6.3.0 + archive: 3.6.1 + crypto: 3.0.5 + markdown_widget: 2.3.2+6 + flutter_colorpicker: 1.1.0 + geolocator: 13.0.1 + shared_preferences: 2.3.2 + isar: 4.0.0-dev.14 + isar_flutter_libs: 4.0.0-dev.14 + fluttertoast: 8.2.8 + cached_network_image: 3.4.1 + video_player: 2.9.1 + audioplayers: 6.0.0 + record: 5.1.2 + duration: 4.0.3 + cupertino_icons: 1.0.8 + dynamic_color: 1.7.0 + supabase_flutter: 2.6.0 + flutter_staggered_grid_view: 0.7.0 + intl: 0.19.0 + flutter_localizations: + sdk: flutter + +dependency_overrides: + web: 1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: 2.4.12 + + # 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: 4.0.0 + msix: 3.16.8 + +# 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: + generate: true + fonts: + - family: qweather + fonts: + - asset: fonts/qweather-icons.ttf + + uses-material-design: true \ No newline at end of file diff --git a/res/screenshot/phone1.png b/res/screenshot/phone1.png new file mode 100644 index 0000000..c7efcb3 Binary files /dev/null and b/res/screenshot/phone1.png differ diff --git a/res/screenshot/phone2.png b/res/screenshot/phone2.png new file mode 100644 index 0000000..d4b7a84 Binary files /dev/null and b/res/screenshot/phone2.png differ diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..e7d1c47 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(mood_diary LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "mood_diary") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..19f5f3f --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,44 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); + DynamicColorPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); + IsarFlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + RecordWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..3c05ac9 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,34 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + app_links + audioplayers_windows + dynamic_color + file_selector_windows + geolocator_windows + isar_flutter_libs + local_auth_windows + permission_handler_windows + record_windows + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..e1c7505 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "cn.yooss" "\0" + VALUE "FileDescription", "mood_diary" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "mood_diary" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 cn.yooss. All rights reserved." "\0" + VALUE "OriginalFilename", "mood_diary.exe" "\0" + VALUE "ProductName", "mood_diary" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..325c855 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"mood_diary", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..b7e9486 Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_