├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── history_of_me │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── fonts │ └── PlayfairDisplay │ │ ├── PlayfairDisplay-Black.ttf │ │ ├── PlayfairDisplay-BlackItalic.ttf │ │ ├── PlayfairDisplay-Bold.ttf │ │ ├── PlayfairDisplay-BoldItalic.ttf │ │ ├── PlayfairDisplay-Italic.ttf │ │ └── PlayfairDisplay-Regular.ttf ├── icon │ ├── Launcher_Icon_Adaptive.png │ ├── Launcher_Icon_Static.png │ └── Launcher_Icon_Static_Dev.png ├── images │ ├── Cloud.png │ ├── Curtain_Left.png │ ├── Curtain_Right.png │ ├── History_Of_Me_Key_64px-01.png │ ├── History_Of_Me_Key_Icon_256px-01.png │ ├── History_Of_Me_Logo_Final-01.png │ ├── History_Of_Me_Window_Artwork_Small.png │ ├── Key.png │ ├── Window.png │ └── lit_Life_Software_Dark_Mode-01.png └── misc │ ├── Google_Playstore_Promo_Image_2.png │ ├── History_Of_Me_Screenshot_1.png │ ├── History_Of_Me_Screenshot_2.png │ ├── History_Of_Me_Screenshot_3.png │ ├── History_Of_Me_Screenshot_4.png │ └── History_Of_Me_Screenshot_5.png ├── build_production.sh ├── flutter_launcher_icons.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-50x50@1x.png │ │ ├── Icon-App-50x50@2x.png │ │ ├── Icon-App-57x57@1x.png │ │ ├── Icon-App-57x57@2x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-72x72@1x.png │ │ ├── Icon-App-72x72@2x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── api.dart ├── api │ ├── all_data_provider.dart │ ├── app_api.dart │ ├── app_settings_provider.dart │ ├── database_state_validator.dart │ ├── debug_output_service.dart │ ├── default_data.dart │ ├── diary_entry_provider.dart │ ├── query_controller.dart │ ├── query_diary_entry_provider.dart │ ├── user_created_color_provider.dart │ └── user_data_provider.dart ├── app.dart ├── controller │ ├── autosave_controller.dart │ ├── diary_photo_picker.dart │ ├── hom_navigator.dart │ └── mood_translator.dart ├── controllers.dart ├── extensions.dart ├── extensions │ ├── file_system_entity_extension.dart │ └── string_extension.dart ├── localization.dart ├── localization │ ├── app_localizations.dart │ ├── app_localizations_delegate.dart │ ├── app_localizations_keys.dart │ └── languages │ │ ├── de.dart │ │ └── en.dart ├── main.dart ├── model │ ├── app_settings.dart │ ├── app_settings.g.dart │ ├── diary_backup.dart │ ├── diary_entry.dart │ ├── diary_entry.g.dart │ ├── diary_photo.dart │ ├── diary_photo.g.dart │ ├── models.dart │ ├── user_created_color.dart │ ├── user_created_color.g.dart │ ├── user_data.dart │ └── user_data.g.dart ├── models.dart ├── screens.dart ├── screens │ ├── app_credits_screen.dart │ ├── app_onboarding_screen.dart │ ├── app_privacy_screen.dart │ ├── backup_notice_screen.dart │ ├── bookmark_editing_screen.dart │ ├── diary_screen.dart │ ├── entry_detail_screen.dart │ ├── entry_editing_screen.dart │ ├── home_screen.dart │ ├── profile_screen.dart │ ├── restore_diary_screen.dart │ ├── select_backup_screen.dart │ └── splash_screen.dart ├── static.dart ├── static │ └── app_assets.dart ├── styles.dart ├── styles │ └── app_colors.dart ├── widgets.dart └── widgets │ ├── animated_updated_label.dart │ ├── app_about_dialog.dart │ ├── app_artwork.dart │ ├── bookmark_back.dart │ ├── bookmark_back_preview.dart │ ├── bookmark_container.dart │ ├── bookmark_cover.dart │ ├── bookmark_design.dart │ ├── bookmark_front.dart │ ├── bookmark_front_preview.dart │ ├── bookmark_page_view.dart │ ├── bookmark_preview_container.dart │ ├── bookmark_title.dart │ ├── cancel_restoring_dialog.dart │ ├── change_name_dialog.dart │ ├── clean_text_field.dart │ ├── create_entry_dialog.dart │ ├── create_new_diary_action_card.dart │ ├── database_state_screen_builder.dart │ ├── deletable_container.dart │ ├── delete_all_photos_dialog.dart │ ├── diary_backup_dialog.dart │ ├── diary_bookmark_header.dart │ ├── diary_entry_bottom_sheet.dart │ ├── diary_filter_header.dart │ ├── diary_filter_header_delegate.dart │ ├── diary_list_tile.dart │ ├── diary_list_view.dart │ ├── diary_preview_card.dart │ ├── dotted_design.dart │ ├── editable_item_meta_info.dart │ ├── ellipse_icon.dart │ ├── entry_detail_backdrop.dart │ ├── entry_detail_card.dart │ ├── greetings_bar.dart │ ├── history_of_me_app_logo.dart │ ├── home_screen_drawer.dart │ ├── image_preview_dialog.dart │ ├── launcher_icon_art.dart │ ├── lit_toggle_button_group.dart │ ├── pattern_config_card.dart │ ├── photos_missing_dialog.dart │ ├── pick_photos_button.dart │ ├── primary_color_selector_card.dart │ ├── purple_pink_button.dart │ ├── purple_pink_save_button.dart │ ├── quote_card.dart │ ├── secondary_color_selector_card.dart │ ├── selectable_color_tile.dart │ ├── selected_create_tile.dart │ ├── statistics_card.dart │ ├── striped_design.dart │ ├── unselected_create_tile.dart │ ├── unsupported_file_dialog.dart │ ├── updated_label_text.dart │ ├── user_icon.dart │ ├── user_profile_card.dart │ └── word_count_badge.dart ├── pubspec.lock ├── pubspec.yaml └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | # Production APK/bundle export directory 34 | /dist/ 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Android Studio will place build artifacts here 46 | /android/app/debug 47 | /android/app/profile 48 | /android/app/release 49 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 55289324c6d1db9071fb372f9f15f5ad5a064eeb 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 LitLifeSoftware. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of LitLifeSoftware nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion 33 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.litlifesoftware.historyofme" 48 | minSdkVersion 23 49 | targetSdkVersion 33 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | signingConfigs { 55 | release { 56 | keyAlias keystoreProperties['keyAlias'] 57 | keyPassword keystoreProperties['keyPassword'] 58 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 59 | storePassword keystoreProperties['storePassword'] 60 | } 61 | } 62 | 63 | buildTypes { 64 | release { 65 | signingConfig signingConfigs.release 66 | } 67 | } 68 | 69 | } 70 | 71 | flutter { 72 | source '../..' 73 | } 74 | 75 | dependencies { 76 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 77 | } 78 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/history_of_me/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.litlifesoftware.historyofme 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffefe1 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | History of Me 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.20' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-Black.ttf -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-BlackItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-Italic.ttf -------------------------------------------------------------------------------- /assets/fonts/PlayfairDisplay/PlayfairDisplay-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/fonts/PlayfairDisplay/PlayfairDisplay-Regular.ttf -------------------------------------------------------------------------------- /assets/icon/Launcher_Icon_Adaptive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/icon/Launcher_Icon_Adaptive.png -------------------------------------------------------------------------------- /assets/icon/Launcher_Icon_Static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/icon/Launcher_Icon_Static.png -------------------------------------------------------------------------------- /assets/icon/Launcher_Icon_Static_Dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/icon/Launcher_Icon_Static_Dev.png -------------------------------------------------------------------------------- /assets/images/Cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/Cloud.png -------------------------------------------------------------------------------- /assets/images/Curtain_Left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/Curtain_Left.png -------------------------------------------------------------------------------- /assets/images/Curtain_Right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/Curtain_Right.png -------------------------------------------------------------------------------- /assets/images/History_Of_Me_Key_64px-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/History_Of_Me_Key_64px-01.png -------------------------------------------------------------------------------- /assets/images/History_Of_Me_Key_Icon_256px-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/History_Of_Me_Key_Icon_256px-01.png -------------------------------------------------------------------------------- /assets/images/History_Of_Me_Logo_Final-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/History_Of_Me_Logo_Final-01.png -------------------------------------------------------------------------------- /assets/images/History_Of_Me_Window_Artwork_Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/History_Of_Me_Window_Artwork_Small.png -------------------------------------------------------------------------------- /assets/images/Key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/Key.png -------------------------------------------------------------------------------- /assets/images/Window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/Window.png -------------------------------------------------------------------------------- /assets/images/lit_Life_Software_Dark_Mode-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/images/lit_Life_Software_Dark_Mode-01.png -------------------------------------------------------------------------------- /assets/misc/Google_Playstore_Promo_Image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/Google_Playstore_Promo_Image_2.png -------------------------------------------------------------------------------- /assets/misc/History_Of_Me_Screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/History_Of_Me_Screenshot_1.png -------------------------------------------------------------------------------- /assets/misc/History_Of_Me_Screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/History_Of_Me_Screenshot_2.png -------------------------------------------------------------------------------- /assets/misc/History_Of_Me_Screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/History_Of_Me_Screenshot_3.png -------------------------------------------------------------------------------- /assets/misc/History_Of_Me_Screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/History_Of_Me_Screenshot_4.png -------------------------------------------------------------------------------- /assets/misc/History_Of_Me_Screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/assets/misc/History_Of_Me_Screenshot_5.png -------------------------------------------------------------------------------- /build_production.sh: -------------------------------------------------------------------------------- 1 | #/!/bin/bash 2 | 3 | now=`date +"%Y%m%d-%H%M"` 4 | appName="History of Me" 5 | appNameClean="HistoryOfMe" 6 | arm64="arm64-v8a" 7 | arm32="armeabi-v7a" 8 | x86="x86_64" 9 | platform="Android" 10 | green=`tput setaf 2` 11 | yellow=`tput setaf 3` 12 | blue=`tput setaf 4` 13 | reset=`tput sgr0` 14 | 15 | echo "${blue}Creating '${appName}' builds for ${now}.\n${reset}" 16 | echo "Building APK files ..." 17 | flutter build apk --split-per-abi 18 | echo "${green}Finished building APK files.${reset}" 19 | echo "Building AppBundle file ...\n" 20 | flutter build appbundle 21 | echo "${green}Finished building AppBundle file.\n" 22 | echo "${green}✓ Finished creating '${appName}' builds.\n${reset}" 23 | echo "Exporting files ...\n" 24 | mkdir -p -v ./dist/"${now}" 25 | mkdir -p -v ./dist/"${now}"/apk 26 | mkdir -p -v ./dist/"${now}"/appbundle 27 | cat <./dist/"${now}"/release_notes_play.txt 28 | 29 | - Bug fixes and stability improvements 30 | 31 | 32 | - Fehlerbehebungen und Stabilitätsverbesserungen 33 | 34 | EOF 35 | cat <./dist/"${now}"/release_notes_github.txt 36 | ## What's new? 37 | 38 | - Bug fixes and stability improvements 39 | EOF 40 | rsync -rvh --progress ./build/app/outputs/apk/ ./dist/"${now}"/apk 41 | rsync -rvh --progress ./build/app/outputs/bundle/ ./dist/"${now}"/appbundle 42 | echo "\n" 43 | echo "Renaming builds ...\n" 44 | 45 | # Rename and relocate arm64 build 46 | mv ./dist/"${now}"/apk/release/app-arm64-v8a-release.apk ./dist/"${now}"/apk/"${appNameClean}-${now}-${platform}-${arm64}.apk" 47 | 48 | # Rename and relocate arm32 build 49 | mv ./dist/"${now}"/apk/release/app-armeabi-v7a-release.apk ./dist/"${now}"/apk/"${appNameClean}-${now}-${platform}-${arm32}.apk" 50 | 51 | # Rename and relocate x86 build 52 | mv ./dist/"${now}"/apk/release/app-x86_64-release.apk ./dist/"${now}"/apk/"${appNameClean}-${now}-${platform}-${x86}.apk" 53 | 54 | # Rename and relocate app bundle 55 | mv ./dist/"${now}"/appbundle/release/app-release.aab ./dist/"${now}"/appbundle/"${appNameClean}-${now}-${platform}.aab" 56 | 57 | # Delete debug builds if existing 58 | rm -rf ./dist/"${now}"/apk/debug 59 | # Delete empty folders 60 | rm -rf ./dist/"${now}"/apk/release 61 | rm -rf ./dist/"${now}"/appbundle/release 62 | 63 | echo "${green}✓ Finished building and exporting '${appName}' releases.\n${reset}" 64 | echo "${yellow}Please edit the release notes before uploading your production builds.\n${reset}" 65 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | dev_dependencies: 2 | flutter_launcher_icons: "^0.10.0" 3 | 4 | flutter_icons: 5 | android: true 6 | ios: true 7 | image_path: "assets/icon/Launcher_Icon_Static.png" 8 | adaptive_icon_background: "#ffefe1" 9 | adaptive_icon_foreground: "assets/icon/Launcher_Icon_Adaptive.png" 10 | remove_alpha_ios: true -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | History of Me 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/api.dart: -------------------------------------------------------------------------------- 1 | /// Classes implementing interfaces allowing History of Me to interact with 2 | /// a local `Hive` database instance. 3 | /// 4 | /// To use, import `package:history_of_me/api.dart`. 5 | library api; 6 | 7 | import 'dart:math'; 8 | 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:flutter/widgets.dart'; 11 | import 'package:history_of_me/extensions.dart'; 12 | import 'package:history_of_me/models.dart'; 13 | import 'package:hive_flutter/hive_flutter.dart'; 14 | import 'package:leitmotif/leitmotif.dart'; 15 | 16 | part 'api/all_data_provider.dart'; 17 | part 'api/app_api.dart'; 18 | part 'api/app_settings_provider.dart'; 19 | part 'api/database_state_validator.dart'; 20 | part 'api/debug_output_service.dart'; 21 | part 'api/default_data.dart'; 22 | part 'api/diary_entry_provider.dart'; 23 | part 'api/query_controller.dart'; 24 | part 'api/query_diary_entry_provider.dart'; 25 | part 'api/user_created_color_provider.dart'; 26 | part 'api/user_data_provider.dart'; 27 | -------------------------------------------------------------------------------- /lib/api/all_data_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// An `api` widget allowing to pass all data stored on the `Hive` database 4 | /// into a child widget using the [builder] method. 5 | /// 6 | /// Accessing all data at once is rather expensive on memory but required to 7 | /// e.g. create backupable objects. 8 | class AllDataProvider extends StatefulWidget { 9 | /// `builder` method allowing to access all data available on its child. 10 | final Widget Function( 11 | BuildContext context, 12 | AppSettings appSettings, 13 | UserData? userData, 14 | List diaryEntries, 15 | List userCreatedColors, 16 | ) builder; 17 | 18 | /// Creates an [AllDataProvider] that provides all data stored on the `Hive` 19 | /// database. 20 | const AllDataProvider({ 21 | Key? key, 22 | required this.builder, 23 | }) : super(key: key); 24 | 25 | @override 26 | _AllDataProviderState createState() => _AllDataProviderState(); 27 | } 28 | 29 | class _AllDataProviderState extends State { 30 | /// The app api instance. 31 | final AppAPI _api = AppAPI(); 32 | 33 | /// The database validator instance. 34 | late DatabaseStateValidator _validator; 35 | 36 | @override 37 | void initState() { 38 | _validator = DatabaseStateValidator(api: _api); 39 | super.initState(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return AppSettingsProvider( 45 | validator: _validator, 46 | builder: (context, appSettings) { 47 | return UserDataProvider( 48 | builder: (context, userData) { 49 | return DiaryEntryProvider( 50 | builder: (context, diaryEntries) { 51 | return UserCreatedColorProvider( 52 | builder: (context, userCreatedColors) { 53 | return widget.builder( 54 | context, 55 | appSettings, 56 | userData, 57 | diaryEntries, 58 | userCreatedColors, 59 | ); 60 | }, 61 | ); 62 | }, 63 | ); 64 | }, 65 | ); 66 | }, 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/api/app_settings_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` widget providing [AppSettings] objects to the child widget accessed 4 | /// by on the builder method. 5 | class AppSettingsProvider extends StatelessWidget { 6 | /// The builder method providing access to [DiaryEntry] objects. 7 | final Widget Function( 8 | BuildContext context, 9 | AppSettings appSettings, 10 | ) builder; 11 | 12 | /// The [AppAPI] instance. 13 | final AppAPI api; 14 | 15 | /// The [DatabaseStateValidator] instance. 16 | final DatabaseStateValidator? validator; 17 | 18 | /// Creates a [AppSettingsProvider]. 19 | const AppSettingsProvider({ 20 | Key? key, 21 | required this.builder, 22 | this.api = const AppAPI(), 23 | this.validator, 24 | }) : super(key: key); 25 | 26 | /// Creates the initial [AppSettings] instance. 27 | void _createAppSettings() { 28 | api.createAppSettings(); 29 | } 30 | 31 | /// Extracts the content stored inside the `Hive` box. 32 | AppSettings? extractContent(Box box) { 33 | AppSettings? appSettings; 34 | // Try to retrieve the `AppSettings` instance 35 | try { 36 | appSettings = box.getAt(AppAPI.defaultEntryIndex); 37 | // If none found, return null. 38 | if (appSettings == null) return null; 39 | // Validate the app settings object to enforce data integrity. 40 | if (validator != null) validator!.validateAppSettings(appSettings); 41 | } catch (e) { 42 | print(e); 43 | _createAppSettings(); 44 | print('Error while accessing AppSettings object. ' 45 | 'Creating backup AppSettings object ...'); 46 | } 47 | return appSettings; 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return ValueListenableBuilder( 53 | valueListenable: api.getAppSettings(), 54 | builder: (context, Box appSettingsBox, _) { 55 | return builder( 56 | context, 57 | extractContent(appSettingsBox) ?? api.defaultAppSettings, 58 | ); 59 | }, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/api/database_state_validator.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` class allowing to validate and correct the current database 4 | /// content to prevent inconsistency or missing values. 5 | /// 6 | /// Incorrect data could be created whenever the `model` classes are altered 7 | /// while the existing database still contains the deprecated state. 8 | /// 9 | /// Implement database-updating methods for each property added to the 10 | /// `model` classes. 11 | 12 | class DatabaseStateValidator { 13 | final AppAPI api; 14 | 15 | /// Creates a [DatabaseStateValidator]. 16 | const DatabaseStateValidator({ 17 | required this.api, 18 | }); 19 | 20 | void createInstallationID(AppSettings appSettings) { 21 | final _appSettings = AppSettings( 22 | privacyPolicyAgreed: appSettings.privacyPolicyAgreed, 23 | darkMode: appSettings.darkMode, 24 | tabIndex: appSettings.tabIndex, 25 | installationID: AppAPI.generateInstallationID(), 26 | lastBackup: appSettings.lastBackup, 27 | backupNoticeIgnored: appSettings.backupNoticeIgnored, 28 | ); 29 | api.updateAppSettings(_appSettings); 30 | } 31 | 32 | void createLastBackup(AppSettings appSettings) { 33 | final _appSettings = AppSettings( 34 | privacyPolicyAgreed: appSettings.privacyPolicyAgreed, 35 | darkMode: appSettings.darkMode, 36 | tabIndex: appSettings.tabIndex, 37 | installationID: appSettings.installationID, 38 | lastBackup: DefaultData.lastBackup, 39 | backupNoticeIgnored: appSettings.backupNoticeIgnored, 40 | ); 41 | api.updateAppSettings(_appSettings); 42 | } 43 | 44 | void createBackupNoticeIgnored(AppSettings appSettings) { 45 | final _appSettings = AppSettings( 46 | privacyPolicyAgreed: appSettings.privacyPolicyAgreed, 47 | darkMode: appSettings.darkMode, 48 | tabIndex: appSettings.tabIndex, 49 | installationID: appSettings.installationID, 50 | lastBackup: appSettings.lastBackup, 51 | backupNoticeIgnored: DefaultData.backupNoticeIgnored, 52 | ); 53 | api.updateAppSettings(_appSettings); 54 | } 55 | 56 | /// Validates the current app settings box state. 57 | /// 58 | /// Updates the app settings box according to current requirements. 59 | void validateAppSettings(AppSettings appSettings) { 60 | if (appSettings.installationID == null) { 61 | print("`InstallationID` setting missing."); 62 | createInstallationID(appSettings); 63 | print("`AppSettings` updated using generated `installationID`"); 64 | } 65 | 66 | if (appSettings.lastBackup == null) { 67 | print("`lastBackup` setting missing."); 68 | createLastBackup(appSettings); 69 | print("`AppSettings` updated using default data."); 70 | } 71 | 72 | if (appSettings.backupNoticeIgnored == null) { 73 | print("`backupNoticeIgnored` setting missing."); 74 | createBackupNoticeIgnored(appSettings); 75 | print("`AppSettings` updated using default data."); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/api/debug_output_service.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A class allowing to print debug output on various occasions. 4 | class DebugOutputService { 5 | /// Prints a debug message stating that a file does not exists anymore. 6 | static void printImageFileStorageError(String path) { 7 | print( 8 | "Diary Photo on " + "'" + path + "'" + " does not exists anymore.", 9 | ); 10 | } 11 | 12 | /// Prints a debug message stating that a file is already backed up. 13 | static void printBackedUpImageFileDuplicateError(String path) { 14 | print("Image File on " + "'" + path + "'" " already backed up."); 15 | } 16 | 17 | /// Prints a debug message stating that a file has been copied. 18 | static void printImageFileCopied(String path) { 19 | print(path + " copied."); 20 | } 21 | 22 | /// Prints a debug message stating that a file has been linked to a diary 23 | /// entry. 24 | static void printImageFileLinked(String path) { 25 | print("File: " + path + " is linked to a DiaryEntry"); 26 | } 27 | 28 | /// Prints a debug message stating that an unused file has been deleted. 29 | static void printStorageImageFileDeleted(String path) { 30 | print("Unused application file on " + path + " " + "deleted."); 31 | } 32 | 33 | /// Prints a debug message stating that deleting a file has failed. 34 | static void printStorageImageFileDeletionError(String path) { 35 | print("Deleting duplicate file on: " + path + " failed."); 36 | } 37 | 38 | /// Prints a debug message stating that an unused file has been deleted. 39 | static void printBackedUpImageFileDeleted(String path) { 40 | print("Unused backed up image file on " + path + " " + "deleted."); 41 | } 42 | 43 | static void printBackedUpImageFileDeletionError(String path) { 44 | print("Deleting duplicate file on: " + path + " failed."); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/api/default_data.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `History of Me` `api` class containing default values applied on new 4 | /// Hive box objects. 5 | class DefaultData { 6 | static const int minStripeCount = 1; 7 | static int maxStripeCount = 32; 8 | static int minDotSize = 12; 9 | static int maxDotSize = 32; 10 | 11 | /// The initial quote. 12 | static const String quote = 13 | "The way to get started is to quit talking and begin doing."; 14 | 15 | /// The initial quote's author. 16 | static const String quoteAuthor = "Walt Disney"; 17 | 18 | /// The initial design pattern index. 19 | static const int designPatternIndex = 0; 20 | 21 | /// The initial primary color value. 22 | static const int primaryColor = 0xFF757575; 23 | 24 | /// The initial secondary color value. 25 | static const int secondaryColors = 0xFFfff2b0; 26 | 27 | /// A list of initial user created color values. 28 | static const List userCreatedColorValues = [ 29 | const Color(0xFFFAFAFA), 30 | const Color(0xFFF5F5F5), 31 | const Color(0xFFEEEEEE), 32 | const Color(0xFFE0E0E0), 33 | const Color(0xFFD6D6D6), 34 | const Color(0xFFBDBDBD), 35 | const Color(0xFF9E9E9E), 36 | const Color(0xFF757575), 37 | const Color(0xFF616161), 38 | const Color(0xFF424242), 39 | const Color(0xFF303030), 40 | const Color(0xFF212121), 41 | const Color(0xFFfff2b0), 42 | ]; 43 | 44 | /// The default entry title. 45 | static const String diaryEntryTitle = ""; 46 | 47 | /// the default entry content. 48 | static const String diaryEntryContent = ""; 49 | 50 | /// The default diary entry's mood score. 51 | static const double diaryEntryMoodScore = 0.5; 52 | 53 | /// The default diary entry's backdrop id. 54 | static const int diaryEntryBackdropId = 1; 55 | 56 | static const bool agreedPrivacy = true; 57 | static const bool darkMode = false; 58 | static const int tabIndex = 0; 59 | static const String lastBackup = ""; 60 | static const List photos = []; 61 | static const int visitCount = 0; 62 | static const int editCount = 0; 63 | static const bool backupNoticeIgnored = false; 64 | static const int maxDaysBackupOutdated = 2; 65 | 66 | static const Color colorGood = Color(0xFFECFFE9); 67 | static const Color colorBad = Color(0xFFF2E4E4); 68 | static const IconData iconGood = LitIcons.check; 69 | static const IconData iconBad = LitIcons.times; 70 | } 71 | -------------------------------------------------------------------------------- /lib/api/diary_entry_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` widget providing [DiaryEntry] objects to the child widget accessed 4 | /// by on the builder method. 5 | class DiaryEntryProvider extends StatelessWidget { 6 | /// The builder method providing access to [DiaryEntry] objects. 7 | final Widget Function( 8 | BuildContext context, 9 | List diaryEntries, 10 | ) builder; 11 | 12 | /// The [AppAPI] instance. 13 | final AppAPI api; 14 | 15 | /// Creates a [DiaryEntryProvider]. 16 | const DiaryEntryProvider({ 17 | Key? key, 18 | required this.builder, 19 | this.api = const AppAPI(), 20 | }) : super(key: key); 21 | 22 | /// Extracts the content stored inside the `Hive` box. 23 | List extractContent(Box box) { 24 | return box.isNotEmpty ? box.values.toList() : []; 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ValueListenableBuilder( 30 | valueListenable: api.getDiaryEntries(), 31 | builder: (BuildContext context, Box box, Widget? _) { 32 | return builder( 33 | context, 34 | extractContent(box), 35 | ); 36 | }, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/api/query_diary_entry_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` widget allowing to query for a single [DiaryEntry] object by its 4 | /// uid. 5 | /// 6 | /// The builder method provides additional access to metadata required to 7 | /// locate the object inside the collection. 8 | /// 9 | /// Returns null if no diary entry has been found. 10 | class QueryDiaryEntryProvider extends StatelessWidget { 11 | /// The [DiaryEntry]'s uid. 12 | /// 13 | /// Required to query the `Hive` database for a specific [DiaryEntry] object. 14 | final String diaryEntryUid; 15 | 16 | /// The builder method providing access to [DiaryEntry] objects. 17 | final Widget Function( 18 | BuildContext context, 19 | DiaryEntry? diaryEntry, 20 | bool isFirst, 21 | bool isLast, 22 | int boxLength, 23 | ) builder; 24 | 25 | /// The [AppAPI] instance. 26 | final AppAPI api; 27 | 28 | /// Creates a [DiaryEntryProvider]. 29 | const QueryDiaryEntryProvider({ 30 | Key? key, 31 | required this.diaryEntryUid, 32 | required this.builder, 33 | this.api = const AppAPI(), 34 | }) : super(key: key); 35 | 36 | /// Extracts the content stored inside the `Hive` box. 37 | DiaryEntry? extractContent(Box box) { 38 | final DiaryEntry? diaryEntry = box.get(diaryEntryUid); 39 | 40 | return diaryEntry; 41 | } 42 | 43 | /// Returns the last entry's index. 44 | int getLastIndex(Box box) { 45 | final int lastIndex = (box.length - 1); 46 | return lastIndex; 47 | } 48 | 49 | /// Returns the first entry's index. 50 | DiaryEntry? getFirstEntry(Box box) { 51 | return box.isNotEmpty ? box.getAt(0) : null; 52 | } 53 | 54 | /// Returns whether the queried entry is stored on the first index. 55 | bool getIsFirstIndex(Box box) { 56 | if (box.isEmpty) { 57 | return false; 58 | } 59 | 60 | DiaryEntry? first = getFirstEntry(box); 61 | 62 | if (first == null) return false; 63 | 64 | final bool? isFirst = first.uid == diaryEntryUid; 65 | 66 | return isFirst ?? false; 67 | } 68 | 69 | /// Returns whether the queried entry is stored on the last index. 70 | bool getIsLastIndex(Box box) { 71 | if (box.isEmpty) { 72 | return false; 73 | } 74 | 75 | final bool? isFirst = box.getAt(getLastIndex(box))!.uid == diaryEntryUid; 76 | return isFirst ?? false; 77 | } 78 | 79 | /// Returns the provided [Box]'s length. 80 | int getBoxLenth(Box box) { 81 | return box.length; 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return ValueListenableBuilder( 87 | valueListenable: api.getDiaryEntries(), 88 | builder: (BuildContext context, Box box, Widget? _) { 89 | return builder( 90 | context, 91 | extractContent(box), 92 | getIsFirstIndex(box), 93 | getIsLastIndex(box), 94 | getBoxLenth(box), 95 | ); 96 | }, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/api/user_created_color_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` widget providing [UserCreatedColor] objects to the child widget 4 | /// accessed by on the builder method. 5 | class UserCreatedColorProvider extends StatelessWidget { 6 | /// The builder method providing access to [UserCreatedColor] objects. 7 | final Widget Function( 8 | BuildContext context, 9 | List userCreatedColors, 10 | ) builder; 11 | 12 | /// The [AppAPI] instance. 13 | final AppAPI api; 14 | 15 | /// Creates a [UserCreatedColorProvider]. 16 | const UserCreatedColorProvider({ 17 | Key? key, 18 | required this.builder, 19 | this.api = const AppAPI(), 20 | }) : super(key: key); 21 | 22 | /// Extracts the content stored inside the `Hive` box. 23 | List extractContent(Box box) { 24 | return box.isNotEmpty ? box.values.toList() : []; 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return ValueListenableBuilder( 30 | valueListenable: api.getUserCreatedColors(), 31 | builder: ( 32 | BuildContext context, 33 | Box box, 34 | Widget? _, 35 | ) { 36 | return builder( 37 | context, 38 | extractContent(box), 39 | ); 40 | }, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/api/user_data_provider.dart: -------------------------------------------------------------------------------- 1 | part of api; 2 | 3 | /// A `api` widget providing [UserData] objects to the child widget accessed 4 | /// by on the builder method. 5 | /// 6 | /// Accessing the user data using this provider should only be done once the 7 | /// user data is already present in the `Hive` database. 8 | class UserDataProvider extends StatelessWidget { 9 | /// The builder method providing access to the [UserData] object. 10 | /// 11 | /// The [isEmpty] values states whether the corresponding box is empty. 12 | final Widget Function( 13 | BuildContext context, 14 | UserData? userData, 15 | ) builder; 16 | 17 | /// The [AppAPI] instance. 18 | final AppAPI api; 19 | 20 | /// Creates a [UserDataProvider]. 21 | const UserDataProvider({ 22 | Key? key, 23 | required this.builder, 24 | this.api = const AppAPI(), 25 | }) : super(key: key); 26 | 27 | /// Extracts the content stored inside the `Hive` box. 28 | /// 29 | /// In case the user has not created an initial `UserData` object, null in 30 | /// favor of an empty object is returned to check whether or not it's the 31 | /// first app startup. 32 | UserData? extractContent(Box box) { 33 | return box.isEmpty ? null : box.getAt(0); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return ValueListenableBuilder( 39 | valueListenable: api.getUserData(), 40 | builder: (context, Box userDataBox, _) { 41 | return builder( 42 | context, 43 | extractContent(userDataBox), 44 | ); 45 | }, 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | import 'package:history_of_me/localization.dart'; 4 | import 'package:history_of_me/static.dart'; 5 | import 'package:history_of_me/widgets.dart'; 6 | import 'package:leitmotif/leitmotif.dart'; 7 | 8 | /// The main Flutter widget of `HistoryOfMe`. 9 | /// 10 | /// It's main purpose is to initialize the [MaterialApp] widget and to load the 11 | /// read-only content from the local storage and to provide basic meta data such 12 | /// as the app name. 13 | /// 14 | /// It also provides the option to restart the whole app if required using 15 | /// its `restartApp()` method. 16 | class App extends StatefulWidget { 17 | /// States whether the app runs in debug mode. 18 | /// 19 | /// This will prevent debug output to be printed. 20 | static const bool DEBUG = false; 21 | 22 | /// The application's name. 23 | static const String appName = "History Of Me"; 24 | 25 | /// The application's slogan. 26 | static const String appSlogan = "Your own personal diary."; 27 | 28 | /// The application's developer. 29 | static const String appDeveloper = "LitLifeSoftware"; 30 | 31 | static const supportedLocales = AppLocalizations.supportedLocales; 32 | 33 | static const supportedLanguages = const [ 34 | EN.languageCode, 35 | DE.languageCode, 36 | ]; 37 | 38 | /// Restarts the whole application by creating a new [UniqueKey] on the 39 | /// uppermost widget ([MaterialApp]). 40 | static void restartApp(BuildContext context) { 41 | context.findAncestorStateOfType<_AppState>()!.restartApp(); 42 | } 43 | 44 | @override 45 | _AppState createState() => _AppState(); 46 | } 47 | 48 | class _AppState extends State { 49 | Key _key = UniqueKey(); 50 | 51 | /// The background photos fetched from local storage. 52 | List backdropPhotoUrlList = []; 53 | 54 | /// The list of images required to load from local storage. 55 | final List utilityImagesUrlList = const [ 56 | AppAssets.keyLogo256px, 57 | AppAssets.keyIcon, 58 | AppAssets.curtainLeftImg, 59 | AppAssets.curtainRightImg, 60 | AppAssets.windowImg, 61 | AppAssets.cloudImg, 62 | AppAssets.historyOfMeArtworkSmall, 63 | ]; 64 | 65 | /// Restart the app globally by creating a new [UniqueKey]. 66 | void restartApp() { 67 | setState(() { 68 | _key = UniqueKey(); 69 | }); 70 | } 71 | 72 | @override 73 | void initState() { 74 | super.initState(); 75 | ImageCacheController( 76 | context: context, 77 | assetImages: utilityImagesUrlList, 78 | ); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return KeyedSubtree( 84 | key: _key, 85 | child: MaterialApp( 86 | debugShowCheckedModeBanner: App.DEBUG, 87 | theme: ThemeData( 88 | brightness: Brightness.light, 89 | primarySwatch: Colors.grey, 90 | primaryColor: Colors.grey[50], 91 | useMaterial3: true, 92 | textTheme: LitSansSerifStyles.theme, 93 | iconTheme: IconThemeData( 94 | color: LitColors.grey500, 95 | ), 96 | appBarTheme: AppBarTheme( 97 | iconTheme: IconThemeData(color: LitColors.grey500), 98 | actionsIconTheme: IconThemeData( 99 | color: LitColors.grey500, 100 | ), 101 | ), 102 | ), 103 | localizationsDelegates: const [ 104 | AppLocalizations.delegate, 105 | LeitmotifLocalizations.delegate, 106 | GlobalMaterialLocalizations.delegate, 107 | GlobalWidgetsLocalizations.delegate, 108 | GlobalCupertinoLocalizations.delegate, 109 | ], 110 | 111 | /// Supported languages 112 | supportedLocales: App.supportedLocales, 113 | title: App.appName, 114 | home: DatabaseStateScreenBuilder(), 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/controller/autosave_controller.dart: -------------------------------------------------------------------------------- 1 | part of controllers; 2 | 3 | /// A `controller` class handling autosave events. 4 | /// 5 | /// Executes the [saveChanges] methods whenenver the preferred 6 | /// duration has been reached. 7 | class AutosaveController { 8 | /// Handles the save action. 9 | final void Function() saveChanges; 10 | 11 | /// The timer's preferred duration. 12 | final Duration duration; 13 | 14 | /// Creates a [AutosaveController]. 15 | /// 16 | /// Initializes the autosave timer. 17 | AutosaveController( 18 | this.saveChanges, { 19 | this.duration = defaultDuration, 20 | }) { 21 | _init(); 22 | } 23 | 24 | /// The timer's default duration. 25 | static const defaultDuration = const Duration(seconds: 60); 26 | 27 | /// The timer to handle calling the [saveChanges] method. 28 | late Timer timer; 29 | 30 | /// Disposes the [AutosaveController] by canceling the timer. 31 | void dispose() { 32 | timer.cancel(); 33 | } 34 | 35 | /// Initializes the [AutosaveController]. 36 | void _init() { 37 | timer = Timer.periodic( 38 | duration, 39 | (_) => { 40 | saveChanges(), 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/controller/hom_navigator.dart: -------------------------------------------------------------------------------- 1 | part of controllers; 2 | 3 | /// A controller class enabling to navigate through screens by its corresponding 4 | /// member method. 5 | /// 6 | /// Pass the [BuildContext] in order to manipulate the widget stack. 7 | class HOMNavigator { 8 | /// The [BuildContext] instance required to initialize the [LitRouteController.] 9 | final BuildContext context; 10 | 11 | /// Creates a [HOMNavigator]. 12 | /// 13 | /// Pass on the [BuildContext] instance. 14 | HOMNavigator(this.context) { 15 | _routeController = LitRouteController(context); 16 | } 17 | 18 | /// The [LitRouteController] will handle the navigation logic. 19 | late LitRouteController _routeController; 20 | 21 | /// Navigates the user to the [EntryDetailScreen]. 22 | /// 23 | /// Provide the arguments that should be passed to the screen widget. 24 | void toDiaryEntryDetailScreen({ 25 | required int listIndex, 26 | required DiaryEntry diaryEntry, 27 | }) { 28 | final Widget pushedWidget = EntryDetailScreen( 29 | listIndex: listIndex, 30 | diaryEntry: diaryEntry, 31 | ); 32 | _routeController.pushMaterialWidget(pushedWidget); 33 | } 34 | 35 | /// Navigates the user to the [EntryEditingScreen]. 36 | /// 37 | /// Provide the arguments that should be passed to the screen widget. 38 | void toEntryEditingScreen({ 39 | required DiaryEntry diaryEntry, 40 | }) { 41 | final Widget pushedWidget = EntryEditingScreen( 42 | diaryEntry: diaryEntry, 43 | ); 44 | _routeController.pushCupertinoWidget(pushedWidget); 45 | } 46 | 47 | /// Navigates the user to the [BookmarkEditingScreen]. 48 | /// 49 | /// Provide the arguments that should be passed to the screen widget. 50 | void toBookmarkEditingScreen({ 51 | required UserData? userData, 52 | }) { 53 | final pushedWidget = BookmarkEditingScreen( 54 | initialUserDataModel: userData, 55 | ); 56 | _routeController.pushMaterialWidget(pushedWidget); 57 | } 58 | 59 | void showCreateEntryDialog() { 60 | _routeController.showDialogWidget( 61 | CreateEntryDialog(), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/controller/mood_translator.dart: -------------------------------------------------------------------------------- 1 | part of controllers; 2 | 3 | /// A `controller` class translating the provided [moodScore] into a human 4 | /// readable label. 5 | class MoodTranslator { 6 | /// The current [BuildContext]. 7 | final BuildContext context; 8 | 9 | /// A list of mood labels sorted accending according to the corresponding 10 | /// [moodScore] it should represent. 11 | /// 12 | /// Applies default labels if none are provided. 13 | final List? labels; 14 | 15 | /// The current mood score. 16 | final double moodScore; 17 | const MoodTranslator({ 18 | required this.moodScore, 19 | required this.context, 20 | this.labels, 21 | }); 22 | 23 | /// A list of mood labels sorted accending according to the corresponding 24 | /// [moodScore] it should represent. 25 | List get _labels => 26 | labels ?? 27 | [ 28 | AppLocalizations.of(context).badLabel, 29 | AppLocalizations.of(context).mixedLabel, 30 | AppLocalizations.of(context).alrightLabel, 31 | AppLocalizations.of(context).goodLabel, 32 | ]; 33 | 34 | /// Returns a [String] translating the [moodScore] into a human readable 35 | /// label. 36 | String get label { 37 | for (int i = 0; i < _labels.length; i++) { 38 | // States the limit the score should not exceed in order to fit 39 | // the value. 40 | double limit = (i + 1) / _labels.length; 41 | if (moodScore < limit) { 42 | return _labels[i]; 43 | } 44 | } 45 | 46 | return _labels.last; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/controllers.dart: -------------------------------------------------------------------------------- 1 | /// A list of controller classes used to implement certain features using 2 | /// business logic. 3 | library controllers; 4 | 5 | import 'dart:async'; 6 | 7 | import 'dart:io'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:history_of_me/api.dart'; 10 | import 'package:history_of_me/localization.dart'; 11 | import 'package:history_of_me/models.dart'; 12 | import 'package:history_of_me/screens.dart'; 13 | import 'package:history_of_me/widgets.dart'; 14 | import 'package:image_picker/image_picker.dart'; 15 | import 'package:leitmotif/leitmotif.dart'; 16 | import 'package:path/path.dart' as p; 17 | import 'package:path_provider/path_provider.dart'; 18 | 19 | part 'controller/autosave_controller.dart'; 20 | part 'controller/hom_navigator.dart'; 21 | part 'controller/mood_translator.dart'; 22 | part 'controller/diary_photo_picker.dart'; 23 | -------------------------------------------------------------------------------- /lib/extensions.dart: -------------------------------------------------------------------------------- 1 | /// Extensions implementing various additional methods on Dart or Flutter 2 | /// base classes. 3 | /// 4 | /// To use, import `package:history_of_me/extensions.dart`. 5 | library extensions; 6 | 7 | import 'dart:io'; 8 | 9 | part 'extensions/file_system_entity_extension.dart'; 10 | part 'extensions/string_extension.dart'; 11 | -------------------------------------------------------------------------------- /lib/extensions/file_system_entity_extension.dart: -------------------------------------------------------------------------------- 1 | part of extensions; 2 | 3 | /// Extends the abstract [FileSystemEntity] class using additional methods and 4 | /// properties. 5 | extension FileSystemEntityExtention on FileSystemEntity { 6 | String get _splitPattern => "/"; 7 | 8 | String get name { 9 | return this.path.split(_splitPattern).last; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/extensions/string_extension.dart: -------------------------------------------------------------------------------- 1 | part of extensions; 2 | 3 | //TODO: Integrate into Leitmotif 4 | /// Extension on [String] class. 5 | /// 6 | /// Includes various methods for word counting. 7 | extension StringExtension on String { 8 | // The pattern applied to split words inside the string. 9 | static const _wordSplitPattern = ' '; 10 | 11 | /// Validates whether the provided string does represent a valid word. 12 | /// 13 | /// Returns true if so. 14 | bool _validateWord(String word) { 15 | return word.isNotEmpty && !(word.contains(_wordSplitPattern)); 16 | } 17 | 18 | /// Return the total number of words included in the string's value. 19 | int get wordCount { 20 | int counter = 0; 21 | 22 | // Iterate through the split string list and validate each item. 23 | for (String word in this.split(_wordSplitPattern)) { 24 | if (_validateWord(word)) { 25 | counter++; 26 | } 27 | } 28 | 29 | return counter; 30 | } 31 | 32 | /// Removes all whitespaces and returns the cleaned [String]. 33 | String removeSpaces() { 34 | return this.split(" ").join(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/localization.dart: -------------------------------------------------------------------------------- 1 | /// Widgets implementing the localization of History of me. 2 | /// 3 | /// To use, import `package:history_of_me/localization.dart`. 4 | library localization; 5 | 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | part 'localization/app_localizations.dart'; 10 | part 'localization/app_localizations_delegate.dart'; 11 | part 'localization/app_localizations_keys.dart'; 12 | part 'localization/languages/de.dart'; 13 | part 'localization/languages/en.dart'; 14 | -------------------------------------------------------------------------------- /lib/localization/app_localizations_delegate.dart: -------------------------------------------------------------------------------- 1 | part of localization; 2 | 3 | /// A Leitmotif delegate class initializing the [AppLocalizations]. 4 | class AppLocalizationsDelegate extends LocalizationsDelegate { 5 | const AppLocalizationsDelegate(); 6 | 7 | @override 8 | bool isSupported(Locale locale) => 9 | AppLocalizations.languages.contains(locale.languageCode); 10 | 11 | @override 12 | Future load(Locale locale) { 13 | // Returning a SynchronousFuture here because an async "load" operation 14 | // isn't needed to produce an instance of AppLocalizations. 15 | return SynchronousFuture( 16 | AppLocalizations(locale), 17 | ); 18 | } 19 | 20 | @override 21 | bool shouldReload(AppLocalizationsDelegate old) => false; 22 | } 23 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/app.dart'; 3 | import 'package:history_of_me/api.dart'; 4 | 5 | void main() async { 6 | await AppAPI().init(); 7 | runApp(App()); 8 | } 9 | -------------------------------------------------------------------------------- /lib/model/app_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'app_settings.g.dart'; 4 | 5 | /// A model class storing app-specific data, which may only be client-specific. 6 | @HiveType(typeId: 3) 7 | class AppSettings { 8 | @HiveField(0) 9 | final bool privacyPolicyAgreed; 10 | @HiveField(1) 11 | final bool darkMode; 12 | @HiveField(2) 13 | final int tabIndex; 14 | @HiveField(3) 15 | final String? installationID; 16 | @HiveField(4) 17 | final String? lastBackup; 18 | @HiveField(5) 19 | final bool? backupNoticeIgnored; 20 | 21 | /// Creates a [AppSettings] object. 22 | const AppSettings({ 23 | required this.privacyPolicyAgreed, 24 | required this.darkMode, 25 | required this.tabIndex, 26 | required this.installationID, 27 | required this.lastBackup, 28 | required this.backupNoticeIgnored, 29 | }); 30 | 31 | /// Creates a [AppSettings] object by serializing the provided `Map` data. 32 | /// 33 | /// `JSON` decoding must be done before serialization. 34 | factory AppSettings.fromJson(Map json) { 35 | return AppSettings( 36 | privacyPolicyAgreed: json['privacyPolicyAgreed'] as bool, 37 | darkMode: json['darkMode'] as bool, 38 | tabIndex: json['tabIndex'] as int, 39 | installationID: json['installationID'] as String?, 40 | lastBackup: json['lastBackup'] as String?, 41 | backupNoticeIgnored: json['backupNoticeIgnored'] as bool, 42 | ); 43 | } 44 | 45 | /// Creates a `Map` object based on this [AppSettings] object. 46 | /// 47 | /// `JSON` encoding must be done after serialization. 48 | Map toJson() => { 49 | 'privacyPolicyAgreed': privacyPolicyAgreed, 50 | 'darkMode': darkMode, 51 | 'tabIndex': tabIndex, 52 | 'installationID': installationID, 53 | 'lastBackup': lastBackup, 54 | 'backupNoticeIgnored': backupNoticeIgnored, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /lib/model/app_settings.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'app_settings.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class AppSettingsAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 3; 12 | 13 | @override 14 | AppSettings read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return AppSettings( 20 | privacyPolicyAgreed: fields[0] as bool, 21 | darkMode: fields[1] as bool, 22 | tabIndex: fields[2] as int, 23 | installationID: fields[3] as String?, 24 | lastBackup: fields[4] as String?, 25 | backupNoticeIgnored: fields[5] as bool?, 26 | ); 27 | } 28 | 29 | @override 30 | void write(BinaryWriter writer, AppSettings obj) { 31 | writer 32 | ..writeByte(6) 33 | ..writeByte(0) 34 | ..write(obj.privacyPolicyAgreed) 35 | ..writeByte(1) 36 | ..write(obj.darkMode) 37 | ..writeByte(2) 38 | ..write(obj.tabIndex) 39 | ..writeByte(3) 40 | ..write(obj.installationID) 41 | ..writeByte(4) 42 | ..write(obj.lastBackup) 43 | ..writeByte(5) 44 | ..write(obj.backupNoticeIgnored); 45 | } 46 | 47 | @override 48 | int get hashCode => typeId.hashCode; 49 | 50 | @override 51 | bool operator ==(Object other) => 52 | identical(this, other) || 53 | other is AppSettingsAdapter && 54 | runtimeType == other.runtimeType && 55 | typeId == other.typeId; 56 | } 57 | -------------------------------------------------------------------------------- /lib/model/diary_backup.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:history_of_me/models.dart'; 4 | import 'package:lit_backup_service/lit_backup_service.dart'; 5 | 6 | /// A `model` class including all required data to create and read backups of 7 | /// the user's diary. 8 | /// 9 | /// It implements the [BackupModel] in order to be compatible to the 10 | /// `lit_backup_service` package. 11 | class DiaryBackup implements BackupModel { 12 | final String appVersion; 13 | final String backupDate; 14 | final AppSettings appSettings; 15 | final List diaryEntries; 16 | final List userCreatedColors; 17 | final UserData userData; 18 | 19 | /// Creates a [DiaryBackup]. 20 | const DiaryBackup({ 21 | required this.appVersion, 22 | required this.backupDate, 23 | required this.appSettings, 24 | required this.diaryEntries, 25 | required this.userCreatedColors, 26 | required this.userData, 27 | }); 28 | 29 | /// Creates a [DiaryBackup] based on the provided `Map`. 30 | factory DiaryBackup.fromJson(Map json) { 31 | /// Extracts all `DiaryEntry` objects into a list. 32 | final diaryEntries = List.from( 33 | jsonDecode(json['diaryEntries']).map( 34 | (item) => DiaryEntry.fromJson(item), 35 | ), 36 | ); 37 | 38 | /// Extracts all `UserCreatedColor` objects into a list. 39 | final userCreatedColors = List.from( 40 | jsonDecode(json['userCreatedColors']).map( 41 | (item) => UserCreatedColor.fromJson(item), 42 | ), 43 | ); 44 | 45 | return DiaryBackup( 46 | appVersion: json['appVersion'] as String, 47 | backupDate: json['backupDate'] as String, 48 | appSettings: AppSettings.fromJson(json['appSettings']), 49 | diaryEntries: diaryEntries, 50 | userCreatedColors: userCreatedColors, 51 | userData: UserData.fromJson(json['userData']), 52 | ); 53 | } 54 | 55 | @override 56 | Map toJson() { 57 | final diaryEntriesMap = 58 | jsonEncode(diaryEntries.map((item) => item.toJson()).toList()); 59 | final userCreatedColorsMap = 60 | jsonEncode(userCreatedColors.map((item) => item.toJson()).toList()); 61 | return { 62 | 'appVersion': appVersion, 63 | 'backupDate': backupDate, 64 | 'appSettings': appSettings.toJson(), 65 | 'diaryEntries': diaryEntriesMap, 66 | 'userCreatedColors': userCreatedColorsMap, 67 | 'userData': userData.toJson(), 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/model/diary_entry.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'diary_photo.dart'; 4 | import 'package:hive/hive.dart'; 5 | part 'diary_entry.g.dart'; 6 | 7 | /// A model class storing data specific to an individual diary entry. 8 | /// 9 | /// It contains the diaries content as well as meta data. 10 | @HiveType(typeId: 2) 11 | class DiaryEntry { 12 | @HiveField(0) 13 | final String uid; 14 | @HiveField(1) 15 | final String date; 16 | @HiveField(2) 17 | final int created; 18 | @HiveField(3) 19 | final int lastUpdated; 20 | @HiveField(4) 21 | final String title; 22 | @HiveField(5) 23 | final String content; 24 | @HiveField(6) 25 | final double moodScore; 26 | @HiveField(7) 27 | final bool favorite; 28 | @HiveField(8) 29 | final int backdropPhotoId; 30 | @HiveField(9) 31 | final List? photos; 32 | @HiveField(10) 33 | final int? visitCount; 34 | @HiveField(11) 35 | final int? editCount; 36 | 37 | /// Creates a [DiaryEntry] object. 38 | const DiaryEntry({ 39 | required this.uid, 40 | required this.date, 41 | required this.created, 42 | required this.lastUpdated, 43 | required this.title, 44 | required this.content, 45 | required this.moodScore, 46 | required this.favorite, 47 | required this.backdropPhotoId, 48 | required this.photos, 49 | required this.visitCount, 50 | required this.editCount, 51 | }); 52 | 53 | /// Creates a [DiaryEntry] object by serializing the provided `Map` data. 54 | /// 55 | /// `JSON` decoding must be done before serialization. 56 | factory DiaryEntry.fromJson(Map json) { 57 | /// Contains all `DiaryPhoto` objects. 58 | List? photos = []; 59 | 60 | if (json.containsKey('photos')) { 61 | photos = List.from( 62 | jsonDecode(json['photos']).map( 63 | (item) => DiaryPhoto.fromJson(item), 64 | ), 65 | ); 66 | } 67 | 68 | return DiaryEntry( 69 | uid: json['uid'] as String, 70 | date: json['date'] as String, 71 | created: json['created'] as int, 72 | lastUpdated: json['lastUpdated'] as int, 73 | title: json['title'] as String, 74 | content: json['content'] as String, 75 | moodScore: json['moodScore'] as double, 76 | favorite: json['favorite'] as bool, 77 | backdropPhotoId: json['backdropPhotoId'] as int, 78 | photos: photos, 79 | visitCount: json['visitCount'] as int, 80 | editCount: json['editCount'] as int, 81 | ); 82 | } 83 | 84 | /// Creates a `Map` object based on this [DiaryEntry] object. 85 | /// 86 | /// `JSON` encoding must be done after serialization. 87 | Map toJson() { 88 | final photosMap = photos != null 89 | ? jsonEncode(photos!.map((item) => item.toJson()).toList()) 90 | : Map(); 91 | print(photosMap.toString()); 92 | 93 | return { 94 | 'uid': uid, 95 | 'date': date, 96 | 'created': created, 97 | 'lastUpdated': lastUpdated, 98 | 'title': title, 99 | 'content': content, 100 | 'moodScore': moodScore, 101 | 'favorite': favorite, 102 | 'backdropPhotoId': backdropPhotoId, 103 | 'photos': photosMap, 104 | 'visitCount': visitCount, 105 | 'editCount': editCount, 106 | }; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/model/diary_entry.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'diary_entry.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class DiaryEntryAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 2; 12 | 13 | @override 14 | DiaryEntry read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return DiaryEntry( 20 | uid: fields[0] as String, 21 | date: fields[1] as String, 22 | created: fields[2] as int, 23 | lastUpdated: fields[3] as int, 24 | title: fields[4] as String, 25 | content: fields[5] as String, 26 | moodScore: fields[6] as double, 27 | favorite: fields[7] as bool, 28 | backdropPhotoId: fields[8] as int, 29 | photos: (fields[9] as List?)?.cast(), 30 | visitCount: fields[10] as int?, 31 | editCount: fields[11] as int?, 32 | ); 33 | } 34 | 35 | @override 36 | void write(BinaryWriter writer, DiaryEntry obj) { 37 | writer 38 | ..writeByte(12) 39 | ..writeByte(0) 40 | ..write(obj.uid) 41 | ..writeByte(1) 42 | ..write(obj.date) 43 | ..writeByte(2) 44 | ..write(obj.created) 45 | ..writeByte(3) 46 | ..write(obj.lastUpdated) 47 | ..writeByte(4) 48 | ..write(obj.title) 49 | ..writeByte(5) 50 | ..write(obj.content) 51 | ..writeByte(6) 52 | ..write(obj.moodScore) 53 | ..writeByte(7) 54 | ..write(obj.favorite) 55 | ..writeByte(8) 56 | ..write(obj.backdropPhotoId) 57 | ..writeByte(9) 58 | ..write(obj.photos) 59 | ..writeByte(10) 60 | ..write(obj.visitCount) 61 | ..writeByte(11) 62 | ..write(obj.editCount); 63 | } 64 | 65 | @override 66 | int get hashCode => typeId.hashCode; 67 | 68 | @override 69 | bool operator ==(Object other) => 70 | identical(this, other) || 71 | other is DiaryEntryAdapter && 72 | runtimeType == other.runtimeType && 73 | typeId == other.typeId; 74 | } 75 | -------------------------------------------------------------------------------- /lib/model/diary_photo.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive_flutter/hive_flutter.dart'; 2 | 3 | part 'diary_photo.g.dart'; 4 | 5 | /// A `model` class storing data of a image file linked to a diary entry. 6 | @HiveType(typeId: 4) 7 | class DiaryPhoto { 8 | @HiveField(0) 9 | final String uid; 10 | @HiveField(1) 11 | final String date; 12 | @HiveField(2) 13 | final int created; 14 | @HiveField(3) 15 | final String name; 16 | @HiveField(4) 17 | final String path; 18 | 19 | /// Creates a [DiaryPhoto]. 20 | const DiaryPhoto({ 21 | required this.uid, 22 | required this.date, 23 | required this.created, 24 | required this.name, 25 | required this.path, 26 | }); 27 | 28 | /// Creates an [DiaryPhoto] object by serializing the provided `Map` 29 | /// data. 30 | /// 31 | /// `JSON` decoding must be done before serialization. 32 | factory DiaryPhoto.fromJson(Map json) => DiaryPhoto( 33 | uid: json['uid'] as String, 34 | date: json['date'] as String, 35 | created: json['created'] as int, 36 | name: json['name'] as String, 37 | path: json['path'] as String, 38 | ); 39 | 40 | /// Creates a `Map` object based on this [UserCreatedColor] object. 41 | /// 42 | /// `JSON` encoding must be done after serialization. 43 | Map toJson() => { 44 | 'uid': uid, 45 | 'date': date, 46 | 'created': created, 47 | 'name': name, 48 | 'path': path, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /lib/model/diary_photo.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'diary_photo.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class DiaryPhotoAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 4; 12 | 13 | @override 14 | DiaryPhoto read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return DiaryPhoto( 20 | uid: fields[0] as String, 21 | date: fields[1] as String, 22 | created: fields[2] as int, 23 | name: fields[3] as String, 24 | path: fields[4] as String, 25 | ); 26 | } 27 | 28 | @override 29 | void write(BinaryWriter writer, DiaryPhoto obj) { 30 | writer 31 | ..writeByte(5) 32 | ..writeByte(0) 33 | ..write(obj.uid) 34 | ..writeByte(1) 35 | ..write(obj.date) 36 | ..writeByte(2) 37 | ..write(obj.created) 38 | ..writeByte(3) 39 | ..write(obj.name) 40 | ..writeByte(4) 41 | ..write(obj.path); 42 | } 43 | 44 | @override 45 | int get hashCode => typeId.hashCode; 46 | 47 | @override 48 | bool operator ==(Object other) => 49 | identical(this, other) || 50 | other is DiaryPhotoAdapter && 51 | runtimeType == other.runtimeType && 52 | typeId == other.typeId; 53 | } 54 | -------------------------------------------------------------------------------- /lib/model/models.dart: -------------------------------------------------------------------------------- 1 | // /// A list of model classes used to store and mutate data across the History of 2 | // /// me app. 3 | // library models; 4 | 5 | // export 'app_settings.dart'; 6 | // export 'backdrop_photo.dart'; 7 | // export 'diary_backup.dart'; 8 | // export 'diary_entry.dart'; 9 | // export 'user_created_color.dart'; 10 | // export 'user_data.dart'; 11 | -------------------------------------------------------------------------------- /lib/model/user_created_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'user_created_color.g.dart'; 4 | 5 | /// A model class storing an user-created `Color` object. 6 | /// 7 | /// Each color channel is extracted into its own property. 8 | @HiveType(typeId: 1) 9 | class UserCreatedColor { 10 | @HiveField(0) 11 | final String uid; 12 | @HiveField(1) 13 | final int alpha; 14 | @HiveField(2) 15 | final int red; 16 | @HiveField(3) 17 | final int green; 18 | @HiveField(4) 19 | final int blue; 20 | 21 | /// Creates an [UserCreatedColor] object. 22 | const UserCreatedColor({ 23 | required this.uid, 24 | required this.alpha, 25 | required this.red, 26 | required this.green, 27 | required this.blue, 28 | }); 29 | 30 | /// Creates an [UserCreatedColor] object by serializing the provided `Map` 31 | /// data. 32 | /// 33 | /// `JSON` decoding must be done before serialization. 34 | factory UserCreatedColor.fromJson(Map json) { 35 | return UserCreatedColor( 36 | uid: json['uid'] as String, 37 | alpha: json['alpha'] as int, 38 | red: json['red'] as int, 39 | green: json['green'] as int, 40 | blue: json['blue'] as int, 41 | ); 42 | } 43 | 44 | /// Creates a `Map` object based on this [UserCreatedColor] object. 45 | /// 46 | /// `JSON` encoding must be done after serialization. 47 | Map toJson() => { 48 | 'uid': uid, 49 | 'alpha': alpha, 50 | 'red': red, 51 | 'green': green, 52 | 'blue': blue, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /lib/model/user_created_color.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_created_color.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class UserCreatedColorAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | UserCreatedColor read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return UserCreatedColor( 20 | uid: fields[0] as String, 21 | alpha: fields[1] as int, 22 | red: fields[2] as int, 23 | green: fields[3] as int, 24 | blue: fields[4] as int, 25 | ); 26 | } 27 | 28 | @override 29 | void write(BinaryWriter writer, UserCreatedColor obj) { 30 | writer 31 | ..writeByte(5) 32 | ..writeByte(0) 33 | ..write(obj.uid) 34 | ..writeByte(1) 35 | ..write(obj.alpha) 36 | ..writeByte(2) 37 | ..write(obj.red) 38 | ..writeByte(3) 39 | ..write(obj.green) 40 | ..writeByte(4) 41 | ..write(obj.blue); 42 | } 43 | 44 | @override 45 | int get hashCode => typeId.hashCode; 46 | 47 | @override 48 | bool operator ==(Object other) => 49 | identical(this, other) || 50 | other is UserCreatedColorAdapter && 51 | runtimeType == other.runtimeType && 52 | typeId == other.typeId; 53 | } 54 | -------------------------------------------------------------------------------- /lib/model/user_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'user_data.g.dart'; 4 | 5 | /// A model class storing user-specific data as well as the currently edited 6 | /// bookmark configuration. 7 | @HiveType(typeId: 0) 8 | class UserData { 9 | @HiveField(0) 10 | final String name; 11 | @HiveField(1) 12 | final int primaryColor; 13 | @HiveField(2) 14 | final int secondaryColor; 15 | @HiveField(3) 16 | final int stripeCount; 17 | @HiveField(4) 18 | final int dotSize; 19 | @HiveField(5) 20 | final bool animated; 21 | @HiveField(6) 22 | final String quote; 23 | @HiveField(7) 24 | final int designPatternIndex; 25 | @HiveField(8) 26 | final String quoteAuthor; 27 | @HiveField(9) 28 | final int lastUpdated; 29 | @HiveField(10) 30 | final int created; 31 | 32 | /// Creates an [UserData] object. 33 | const UserData({ 34 | required this.name, 35 | required this.primaryColor, 36 | required this.secondaryColor, 37 | required this.stripeCount, 38 | required this.dotSize, 39 | required this.animated, 40 | required this.quote, 41 | required this.designPatternIndex, 42 | required this.quoteAuthor, 43 | required this.lastUpdated, 44 | required this.created, 45 | }); 46 | 47 | /// Creates an [UserData] object by serializing the provided `Map` data. 48 | /// 49 | /// `JSON` decoding must be done before serialization. 50 | factory UserData.fromJson(Map json) { 51 | return UserData( 52 | name: json['name'] as String, 53 | primaryColor: json['primaryColor'] as int, 54 | secondaryColor: json['secondaryColor'] as int, 55 | stripeCount: json['stripeCount'] as int, 56 | dotSize: json['dotSize'] as int, 57 | animated: json['animated'] as bool, 58 | quote: json['quote'] as String, 59 | designPatternIndex: json['designPatternIndex'] as int, 60 | quoteAuthor: json['quoteAuthor'] as String, 61 | lastUpdated: json['lastUpdated'] as int, 62 | created: json['created'] as int, 63 | ); 64 | } 65 | 66 | /// Creates a `Map` object based on this [UserData] object. 67 | /// 68 | /// `JSON` encoding must be done after serialization. 69 | Map toJson() => { 70 | 'name': name, 71 | 'primaryColor': primaryColor, 72 | 'secondaryColor': secondaryColor, 73 | 'stripeCount': stripeCount, 74 | 'dotSize': dotSize, 75 | 'animated': animated, 76 | 'quote': quote, 77 | 'designPatternIndex': designPatternIndex, 78 | 'quoteAuthor': quoteAuthor, 79 | 'lastUpdated': lastUpdated, 80 | 'created': created, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /lib/model/user_data.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_data.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class UserDataAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | UserData read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return UserData( 20 | name: fields[0] as String, 21 | primaryColor: fields[1] as int, 22 | secondaryColor: fields[2] as int, 23 | stripeCount: fields[3] as int, 24 | dotSize: fields[4] as int, 25 | animated: fields[5] as bool, 26 | quote: fields[6] as String, 27 | designPatternIndex: fields[7] as int, 28 | quoteAuthor: fields[8] as String, 29 | lastUpdated: fields[9] as int, 30 | created: fields[10] as int, 31 | ); 32 | } 33 | 34 | @override 35 | void write(BinaryWriter writer, UserData obj) { 36 | writer 37 | ..writeByte(11) 38 | ..writeByte(0) 39 | ..write(obj.name) 40 | ..writeByte(1) 41 | ..write(obj.primaryColor) 42 | ..writeByte(2) 43 | ..write(obj.secondaryColor) 44 | ..writeByte(3) 45 | ..write(obj.stripeCount) 46 | ..writeByte(4) 47 | ..write(obj.dotSize) 48 | ..writeByte(5) 49 | ..write(obj.animated) 50 | ..writeByte(6) 51 | ..write(obj.quote) 52 | ..writeByte(7) 53 | ..write(obj.designPatternIndex) 54 | ..writeByte(8) 55 | ..write(obj.quoteAuthor) 56 | ..writeByte(9) 57 | ..write(obj.lastUpdated) 58 | ..writeByte(10) 59 | ..write(obj.created); 60 | } 61 | 62 | @override 63 | int get hashCode => typeId.hashCode; 64 | 65 | @override 66 | bool operator ==(Object other) => 67 | identical(this, other) || 68 | other is UserDataAdapter && 69 | runtimeType == other.runtimeType && 70 | typeId == other.typeId; 71 | } 72 | -------------------------------------------------------------------------------- /lib/models.dart: -------------------------------------------------------------------------------- 1 | /// A collection of `model` classes storing different kinds of data. 2 | /// 3 | /// Includes the Hive-compatible adapters. 4 | library models; 5 | 6 | export 'model/app_settings.dart'; 7 | export 'model/diary_backup.dart'; 8 | export 'model/diary_entry.dart'; 9 | export 'model/user_created_color.dart'; 10 | export 'model/user_data.dart'; 11 | export 'model/diary_photo.dart'; 12 | -------------------------------------------------------------------------------- /lib/screens.dart: -------------------------------------------------------------------------------- 1 | /// Widgets implementing the navigatable screen widgets of History of me. 2 | /// 3 | /// To use, import `package:history_of_me/screens.dart`. 4 | library screens; 5 | 6 | export 'screens/app_onboarding_screen.dart'; 7 | export 'screens/bookmark_editing_screen.dart'; 8 | export 'screens/diary_screen.dart'; 9 | export 'screens/entry_detail_screen.dart'; 10 | export 'screens/entry_editing_screen.dart'; 11 | export 'screens/home_screen.dart'; 12 | export 'screens/profile_screen.dart'; 13 | export 'screens/restore_diary_screen.dart'; 14 | export 'screens/select_backup_screen.dart'; 15 | export 'screens/splash_screen.dart'; 16 | export 'screens/app_privacy_screen.dart'; 17 | export 'screens/app_credits_screen.dart'; 18 | export 'screens/backup_notice_screen.dart'; 19 | -------------------------------------------------------------------------------- /lib/screens/app_credits_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/app.dart'; 3 | import 'package:history_of_me/localization.dart'; 4 | import 'package:history_of_me/widgets.dart'; 5 | import 'package:leitmotif/leitmotif.dart'; 6 | 7 | class AppCreditsScreen extends StatelessWidget { 8 | const AppCreditsScreen({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return LitCreditsScreen( 13 | art: AppLauncherIconArt( 14 | boxShadow: LitBoxShadows.sm, 15 | ), 16 | appName: App.appName, 17 | appDescription: AppLocalizations.of(context).aboutAppDescr, 18 | credits: [ 19 | CreditData( 20 | role: LeitmotifLocalizations.of(context).creatorLabel, 21 | names: [ 22 | App.appDeveloper, 23 | ], 24 | ), 25 | CreditData( 26 | role: LeitmotifLocalizations.of(context).userExpericenceDesignLabel, 27 | names: [ 28 | AppLocalizations.of(context).creatorName, 29 | ], 30 | ), 31 | CreditData( 32 | role: LeitmotifLocalizations.of(context).developmentLabel, 33 | names: [ 34 | AppLocalizations.of(context).creatorName, 35 | ], 36 | ), 37 | CreditData( 38 | role: AppLocalizations.of(context).inspiredByLabel, 39 | names: [ 40 | AppLocalizations.of(context).inspiredByMovieTitle, 41 | ], 42 | ), 43 | ], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/screens/app_onboarding_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/localization.dart'; 3 | import 'package:history_of_me/widgets.dart'; 4 | import 'package:leitmotif/leitmotif.dart'; 5 | 6 | /// A `screen` widget returning a customized [LitOnboardingScreen]. 7 | /// 8 | /// The screen will display all main features of `History of Me` on a card 9 | /// view. 10 | class AppOnboardingScreen extends StatefulWidget { 11 | final void Function()? onDismiss; 12 | const AppOnboardingScreen({ 13 | Key? key, 14 | this.onDismiss, 15 | }) : super(key: key); 16 | @override 17 | _AppOnboardingScreenState createState() => _AppOnboardingScreenState(); 18 | } 19 | 20 | class _AppOnboardingScreenState extends State { 21 | /// Returns customized localizations for the onboarding screen. 22 | LitOnboardingScreenLocalization get localizations => 23 | LitOnboardingScreenLocalization( 24 | title: LeitmotifLocalizations.of(context).onboardingLabel, 25 | nextLabel: LeitmotifLocalizations.of(context).nextLabel, 26 | dismissLabel: AppLocalizations.of(context).continueLabel, 27 | ); 28 | 29 | /// Handles the `dismiss` action. 30 | void _onDismiss() { 31 | if (widget.onDismiss != null) { 32 | widget.onDismiss!(); 33 | } else { 34 | LitRouteController(context).pop(); 35 | } 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return LitOnboardingScreen( 41 | localization: localizations, 42 | showButtonIcon: false, 43 | art: Padding( 44 | padding: const EdgeInsets.symmetric(vertical: 16.0), 45 | child: SizedBox( 46 | width: MediaQuery.of(context).size.width * 0.55, 47 | child: LayoutBuilder( 48 | builder: (context, constraints) { 49 | return Row( 50 | crossAxisAlignment: CrossAxisAlignment.start, 51 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 52 | children: [ 53 | HistoryOfMeAppLogo( 54 | width: constraints.maxWidth * 0.25, 55 | showKeyImage: true, 56 | color: Colors.white, 57 | ), 58 | AppArtwork( 59 | width: constraints.maxWidth * 0.65, 60 | ), 61 | ], 62 | ); 63 | }, 64 | ), 65 | ), 66 | ), 67 | textItems: [ 68 | TextPageContent( 69 | subtitle: AppLocalizations.of(context).organizeLabel, 70 | title: AppLocalizations.of(context).browseDiaryTitle, 71 | text: AppLocalizations.of(context).browseDiaryDescr, 72 | ), 73 | TextPageContent( 74 | subtitle: AppLocalizations.of(context).reliveLabel, 75 | title: AppLocalizations.of(context).readDiaryTitle, 76 | text: AppLocalizations.of(context).readDiaryDescr, 77 | ), 78 | TextPageContent( 79 | subtitle: AppLocalizations.of(context).personalizeLabel, 80 | title: AppLocalizations.of(context).customizeBookmarkTitle, 81 | text: AppLocalizations.of(context).customizeBookmarkDescr, 82 | ), 83 | TextPageContent( 84 | subtitle: AppLocalizations.of(context).privateLabel, 85 | title: LeitmotifLocalizations.of(context).privacyLabel, 86 | text: AppLocalizations.of(context).privacyDescr, 87 | ), 88 | ], 89 | onDismiss: _onDismiss, 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/screens/app_privacy_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/localization.dart'; 3 | import 'package:history_of_me/widgets.dart'; 4 | import 'package:leitmotif/leitmotif.dart'; 5 | 6 | class AppPrivacyScreen extends StatelessWidget { 7 | const AppPrivacyScreen({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return LitPrivacyPolicyScreen( 12 | onAgreeCallback: () => LitRouteController(context).pop(), 13 | privacyBody: AppLocalizations.of(context).privacyDescr, 14 | art: AppLauncherIconArt(), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/api.dart'; 3 | import 'package:history_of_me/localization.dart'; 4 | import 'package:history_of_me/models.dart'; 5 | import 'package:history_of_me/screens.dart'; 6 | import 'package:history_of_me/widgets.dart'; 7 | 8 | import 'package:leitmotif/leitmotif.dart'; 9 | 10 | /// The app's home screen widget allowing to navigate between multiple tabs. 11 | /// 12 | /// Accessing and creating (if required) the [AppSettings] will be handled 13 | /// within the app. 14 | class HomeScreen extends StatefulWidget { 15 | /// The bookmark animation's [Duration]. 16 | final Duration bookmarkAnimationDuration; 17 | 18 | /// Creates a [HomeScreen]. 19 | /// 20 | /// * [bookmarkAnimationDuration] will determine the bookmark's animation 21 | /// duration on each tab. 22 | const HomeScreen({ 23 | Key? key, 24 | this.bookmarkAnimationDuration = const Duration(milliseconds: 5000), 25 | }) : super(key: key); 26 | @override 27 | _HomeScreenState createState() => _HomeScreenState(); 28 | } 29 | 30 | class _HomeScreenState extends State with TickerProviderStateMixin { 31 | late AnimationController _bookmarkAnimation; 32 | 33 | late AppAPI _api; 34 | 35 | /// Persists the `tabIndex` on the the corresponding [AppSettings] instance. 36 | void _onTabSwitch(int tabIndex, AppSettings appSettings) { 37 | _api.updateTabIndex(appSettings, tabIndex); 38 | } 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | _api = AppAPI(); 44 | _bookmarkAnimation = AnimationController( 45 | duration: widget.bookmarkAnimationDuration, 46 | vsync: this, 47 | )..repeat(reverse: true); 48 | } 49 | 50 | @override 51 | void dispose() { 52 | _bookmarkAnimation.dispose(); 53 | super.dispose(); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return AppSettingsProvider( 59 | api: _api, 60 | validator: DatabaseStateValidator(api: _api), 61 | builder: (BuildContext context, AppSettings appSettings) { 62 | return LitTabView( 63 | materialAppBar: AppBar( 64 | centerTitle: true, 65 | title: Text( 66 | AppLocalizations.of(context).greetingLabel, 67 | textAlign: TextAlign.center, 68 | style: LitSansSerifStyles.subtitle2.copyWith( 69 | fontWeight: FontWeight.bold, 70 | ), 71 | ), 72 | ), 73 | materialDrawer: HomeScreenDrawer(appSettings: appSettings), 74 | initialTabIndex: appSettings.tabIndex, 75 | transitionListener: (index) => _onTabSwitch(index, appSettings), 76 | tabs: [ 77 | LitNavigableTab( 78 | tabData: LitBottomNavigationItemData( 79 | index: 0, 80 | icon: LitIcons.home_alt, 81 | iconAlt: LitIcons.home, 82 | title: AppLocalizations.of(context).homeLabel, 83 | ), 84 | screen: DiaryScreen( 85 | bookmarkAnimation: _bookmarkAnimation, 86 | appSettings: appSettings, 87 | ), 88 | ), 89 | LitNavigableTab( 90 | tabData: LitBottomNavigationItemData( 91 | index: 1, 92 | icon: LitIcons.person, 93 | iconAlt: LitIcons.person_solid, 94 | title: AppLocalizations.of(context).profileLabel, 95 | ), 96 | screen: ProfileScreen( 97 | bookmarkAnimation: _bookmarkAnimation, 98 | ), 99 | ), 100 | ], 101 | ); 102 | }, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/screens/restore_diary_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/localization.dart'; 3 | import 'package:history_of_me/screens.dart'; 4 | import 'package:history_of_me/styles.dart'; 5 | import 'package:history_of_me/widgets.dart'; 6 | import 'package:leitmotif/leitmotif.dart'; 7 | 8 | /// A Flutter `screen` widget allowing to restore an existing diary by reading 9 | /// its backup file or to create a new diary (if restoring the diary is not 10 | /// possible). 11 | /// 12 | class RestoreDiaryScreen extends StatefulWidget { 13 | /// Handles the creation of a new diary. 14 | final void Function() onCreateNewInstance; 15 | 16 | /// Creates a [RestoreDiaryScreen]. 17 | const RestoreDiaryScreen({ 18 | Key? key, 19 | required this.onCreateNewInstance, 20 | }) : super(key: key); 21 | 22 | @override 23 | _RestoreDiaryScreenState createState() => _RestoreDiaryScreenState(); 24 | } 25 | 26 | class _RestoreDiaryScreenState extends State { 27 | late LitRouteController _routeController; 28 | late ScrollController _scrollController; 29 | 30 | /// Handles the `restore` action by navigating to the [SelectBackupScreen]. 31 | void onRestoreBackup() { 32 | _routeController.pushCupertinoWidget( 33 | SelectBackupScreen( 34 | onCreateNewInstance: widget.onCreateNewInstance, 35 | ), 36 | ); 37 | } 38 | 39 | @override 40 | void initState() { 41 | _routeController = LitRouteController(context); 42 | _scrollController = ScrollController(); 43 | super.initState(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return LitScaffold( 49 | appBar: FixedOnScrollTitledAppbar( 50 | scrollController: _scrollController, 51 | title: AppLocalizations.of(context).newDiaryTitle, 52 | ), 53 | body: Container( 54 | height: MediaQuery.of(context).size.height, 55 | width: MediaQuery.of(context).size.width, 56 | decoration: BoxDecoration( 57 | gradient: LitGradients.greyGradient, 58 | ), 59 | child: ScrollableColumn( 60 | controller: _scrollController, 61 | crossAxisAlignment: CrossAxisAlignment.start, 62 | padding: const EdgeInsets.symmetric( 63 | vertical: 16.0, 64 | horizontal: 16.0, 65 | ), 66 | children: [ 67 | LitScreenTitle( 68 | title: AppLocalizations.of(context).newDiaryTitle, 69 | subtitle: AppLocalizations.of(context).newDiarySubtitle, 70 | ), 71 | Padding( 72 | padding: const EdgeInsets.symmetric( 73 | vertical: 16.0, 74 | ), 75 | child: Column( 76 | children: [ 77 | CreateNewActionCard( 78 | onCreate: widget.onCreateNewInstance, 79 | ), 80 | SizedBox(height: 24.0), 81 | _RestoreBackupActionCard( 82 | onRestore: onRestoreBackup, 83 | ), 84 | ], 85 | ), 86 | ) 87 | ], 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | 94 | class _RestoreBackupActionCard extends StatelessWidget { 95 | final void Function() onRestore; 96 | 97 | const _RestoreBackupActionCard({ 98 | Key? key, 99 | required this.onRestore, 100 | }) : super(key: key); 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | return LitTitledActionCard( 105 | title: AppLocalizations.of(context).restoreDiaryTitle, 106 | subtitle: AppLocalizations.of(context).continueJourneyTitle, 107 | actionButtonData: [ 108 | ActionButtonData( 109 | title: LeitmotifLocalizations.of(context).restoreLabel, 110 | onPressed: onRestore, 111 | backgroundColor: AppColors.pastelGreen, 112 | accentColor: AppColors.pastelBlue, 113 | ), 114 | ], 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:history_of_me/widgets.dart'; 3 | import 'package:leitmotif/leitmotif.dart'; 4 | 5 | /// A screen widget to display a minimalist HistoryOfMe logo while the 6 | /// application being loaded. 7 | class SplashScreen extends StatelessWidget { 8 | /// Creates a [SplashScreen]. 9 | const SplashScreen(); 10 | @override 11 | Widget build(BuildContext context) { 12 | return LitStaticLoadingScreen( 13 | child: HistoryOfMeAppLogo( 14 | color: LitColors.grey350, 15 | showKeyImage: false, 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/static.dart: -------------------------------------------------------------------------------- 1 | /// Classes containing static and default values which will be constant during 2 | /// the entire runtime. 3 | /// 4 | /// To use, import `package:history_of_me/static.dart`. 5 | library static; 6 | 7 | part 'static/app_assets.dart'; 8 | -------------------------------------------------------------------------------- /lib/static/app_assets.dart: -------------------------------------------------------------------------------- 1 | part of static; 2 | 3 | /// A `History of Me` static class containg paths to the app's assets. 4 | class AppAssets { 5 | static const String _assetImagesRoot = "assets/images/"; 6 | static const String _iconsRoot = "assets/icon/"; 7 | 8 | /// The `History of Me` key logo. 9 | static const keyLogo64px = _assetImagesRoot + "History_Of_Me_Key_64px-01.png"; 10 | 11 | /// The `History of Me` key logo. 12 | static const keyLogo256px = 13 | _assetImagesRoot + "History_Of_Me_Key_Icon_256px-01.png"; 14 | 15 | static const appIconAdaptive = _iconsRoot + "Launcher_Icon_Adaptive.png"; 16 | 17 | static const keyIcon = _assetImagesRoot + "Key.png"; 18 | 19 | static const curtainLeftImg = _assetImagesRoot + "Curtain_Left.png"; 20 | 21 | static const curtainRightImg = _assetImagesRoot + "Curtain_Right.png"; 22 | 23 | static const windowImg = _assetImagesRoot + "Window.png"; 24 | 25 | static const cloudImg = _assetImagesRoot + "Cloud.png"; 26 | 27 | static const historyOfMeArtworkSmall = 28 | _assetImagesRoot + "History_Of_Me_Window_Artwork_Small.png"; 29 | } 30 | -------------------------------------------------------------------------------- /lib/styles.dart: -------------------------------------------------------------------------------- 1 | /// A collection of styling components and elements applied on History of me. 2 | /// 3 | /// To use, import `package:history_of_me/styles.dart` 4 | 5 | library styles; 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | part 'styles/app_colors.dart'; 10 | -------------------------------------------------------------------------------- /lib/styles/app_colors.dart: -------------------------------------------------------------------------------- 1 | part of styles; 2 | 3 | /// A collection of [Color] objects used in `History of Me`. 4 | class AppColors { 5 | /// A purple `Color`. 6 | static const Color purple = const Color(0xFFDE8FFA); 7 | 8 | /// A pink `Color`. 9 | static const Color pink = const Color(0xFFFA72AA); 10 | 11 | /// A pastel blue `Color`. 12 | static const Color pastelBlue = const Color(0xFFD7ECF4); 13 | 14 | /// A pastel green `Color`. 15 | static const Color pastelGreen = const Color(0xFFC7EBD3); 16 | 17 | /// A pastel pink `Color`. 18 | static const Color pastelPink = const Color(0xFFEBC7CF); 19 | 20 | /// A pastel purple `Color`. 21 | static const Color pastelPurple = const Color(0xFFDFD7F4); 22 | } 23 | -------------------------------------------------------------------------------- /lib/widgets.dart: -------------------------------------------------------------------------------- 1 | /// Widgets implementing various user interface components of History of me. 2 | /// 3 | /// To use, import `package:history_of_me/widgets.dart`. 4 | library widgets; 5 | 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | import 'dart:math'; 9 | import 'dart:ui' as ui; 10 | 11 | import 'package:date_colors/date_colors.dart'; 12 | import 'package:flutter/material.dart'; 13 | import 'package:history_of_me/api.dart'; 14 | import 'package:history_of_me/app.dart'; 15 | import 'package:history_of_me/controllers.dart'; 16 | import 'package:history_of_me/extensions.dart'; 17 | import 'package:history_of_me/localization.dart'; 18 | import 'package:history_of_me/models.dart'; 19 | import 'package:history_of_me/screens.dart'; 20 | import 'package:history_of_me/static.dart'; 21 | import 'package:history_of_me/styles.dart'; 22 | import 'package:leitmotif/leitmotif.dart'; 23 | import 'package:lit_backup_service/lit_backup_service.dart'; 24 | import 'package:lit_relative_date_time/lit_relative_date_time.dart'; 25 | import 'package:package_info_plus/package_info_plus.dart'; 26 | import 'package:path_provider/path_provider.dart'; 27 | 28 | part 'widgets/animated_updated_label.dart'; 29 | part 'widgets/app_artwork.dart'; 30 | part 'widgets/bookmark_back.dart'; 31 | part 'widgets/bookmark_back_preview.dart'; 32 | part 'widgets/bookmark_container.dart'; 33 | part 'widgets/bookmark_cover.dart'; 34 | part 'widgets/bookmark_design.dart'; 35 | part 'widgets/bookmark_front.dart'; 36 | part 'widgets/bookmark_front_preview.dart'; 37 | part 'widgets/bookmark_page_view.dart'; 38 | part 'widgets/bookmark_preview_container.dart'; 39 | part 'widgets/bookmark_title.dart'; 40 | part 'widgets/cancel_restoring_dialog.dart'; 41 | part 'widgets/change_name_dialog.dart'; 42 | part 'widgets/clean_text_field.dart'; 43 | part 'widgets/create_entry_dialog.dart'; 44 | part 'widgets/create_new_diary_action_card.dart'; 45 | part 'widgets/database_state_screen_builder.dart'; 46 | part 'widgets/deletable_container.dart'; 47 | part 'widgets/delete_all_photos_dialog.dart'; 48 | part 'widgets/diary_backup_dialog.dart'; 49 | part 'widgets/diary_bookmark_header.dart'; 50 | part 'widgets/diary_filter_header.dart'; 51 | part 'widgets/diary_filter_header_delegate.dart'; 52 | part 'widgets/diary_list_tile.dart'; 53 | part 'widgets/diary_list_view.dart'; 54 | part 'widgets/diary_preview_card.dart'; 55 | part 'widgets/dotted_design.dart'; 56 | part 'widgets/editable_item_meta_info.dart'; 57 | part 'widgets/ellipse_icon.dart'; 58 | part 'widgets/entry_detail_backdrop.dart'; 59 | part 'widgets/entry_detail_card.dart'; 60 | part 'widgets/greetings_bar.dart'; 61 | part 'widgets/history_of_me_app_logo.dart'; 62 | part 'widgets/launcher_icon_art.dart'; 63 | part 'widgets/home_screen_drawer.dart'; 64 | part 'widgets/image_preview_dialog.dart'; 65 | part 'widgets/lit_toggle_button_group.dart'; 66 | part 'widgets/pattern_config_card.dart'; 67 | part 'widgets/photos_missing_dialog.dart'; 68 | part 'widgets/pick_photos_button.dart'; 69 | part 'widgets/primary_color_selector_card.dart'; 70 | part 'widgets/purple_pink_button.dart'; 71 | part 'widgets/purple_pink_save_button.dart'; 72 | part 'widgets/quote_card.dart'; 73 | part 'widgets/secondary_color_selector_card.dart'; 74 | part 'widgets/selectable_color_tile.dart'; 75 | part 'widgets/selected_create_tile.dart'; 76 | part 'widgets/statistics_card.dart'; 77 | part 'widgets/striped_design.dart'; 78 | part 'widgets/unselected_create_tile.dart'; 79 | part 'widgets/unsupported_file_dialog.dart'; 80 | part 'widgets/updated_label_text.dart'; 81 | part 'widgets/user_icon.dart'; 82 | part 'widgets/user_profile_card.dart'; 83 | part 'widgets/word_count_badge.dart'; 84 | part 'widgets/app_about_dialog.dart'; 85 | part 'widgets/diary_entry_bottom_sheet.dart'; 86 | -------------------------------------------------------------------------------- /lib/widgets/animated_updated_label.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class AnimatedUpdatedLabel extends StatefulWidget { 4 | final int? lastUpdateTimestamp; 5 | final EdgeInsets padding; 6 | 7 | const AnimatedUpdatedLabel({ 8 | Key? key, 9 | required this.lastUpdateTimestamp, 10 | this.padding = const EdgeInsets.symmetric( 11 | vertical: 8.0, 12 | horizontal: 16.0, 13 | ), 14 | }) : super(key: key); 15 | 16 | @override 17 | _AnimatedUpdatedLabelState createState() => _AnimatedUpdatedLabelState(); 18 | } 19 | 20 | class _AnimatedUpdatedLabelState extends State 21 | with TickerProviderStateMixin { 22 | late AnimationController _animationController; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _animationController = AnimationController( 28 | duration: Duration(milliseconds: 1000), 29 | vsync: this, 30 | ); 31 | _animationController.repeat(reverse: true); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | _animationController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return AnimatedBuilder( 43 | animation: _animationController, 44 | builder: (BuildContext context, Widget? _) { 45 | return Padding( 46 | padding: widget.padding, 47 | child: Align( 48 | alignment: Alignment.centerLeft, 49 | child: AnimatedOpacity( 50 | opacity: 0.35 + (0.65 * _animationController.value), 51 | duration: _animationController.duration!, 52 | child: UpdatedLabelText( 53 | lastUpdateTimestamp: widget.lastUpdateTimestamp, 54 | ), 55 | ), 56 | ), 57 | ); 58 | }, 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/widgets/app_about_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A customized [LitAboutDialog] widget displaying the app's about dialog. 4 | class AppAboutDialog extends StatelessWidget { 5 | /// Creates a [AppAboutDialog]. 6 | const AppAboutDialog({Key? key}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return LitAboutDialog( 11 | title: LeitmotifLocalizations.of(context).aboutAppLabel, 12 | appName: App.appName, 13 | art: AppLauncherIconArt( 14 | boxShadow: LitBoxShadows.sm, 15 | ), 16 | infoDescription: AppLocalizations.of(context).aboutAppDescr, 17 | copyrightNotice: AppLocalizations.of(context).copyrightNotice, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/widgets/app_artwork.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class AppArtwork extends StatefulWidget { 4 | final double width; 5 | 6 | const AppArtwork({ 7 | Key? key, 8 | this.width = 280.0, 9 | }) : super(key: key); 10 | 11 | @override 12 | _AppArtworkState createState() => _AppArtworkState(); 13 | } 14 | 15 | class _AppArtworkState extends State with TickerProviderStateMixin { 16 | late AnimationController _animationController; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _animationController = AnimationController( 22 | duration: Duration(milliseconds: 1500), 23 | vsync: this, 24 | ); 25 | _animationController.repeat(reverse: true); 26 | } 27 | 28 | @override 29 | void dispose() { 30 | _animationController.dispose(); 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return SizedBox( 37 | width: widget.width, 38 | child: Stack( 39 | //alignment: Alignment.center, 40 | children: [ 41 | Image( 42 | image: AssetImage( 43 | "assets/images/Window.png", 44 | ), 45 | fit: BoxFit.scaleDown, 46 | 47 | //color: Colors.black, 48 | ), 49 | AnimatedBuilder( 50 | animation: _animationController, 51 | builder: (context, _) { 52 | return Align( 53 | alignment: Alignment.topCenter, 54 | child: SizedBox( 55 | width: widget.width * 0.4, 56 | child: Transform( 57 | transform: Matrix4.translationValues( 58 | 0.0, 59 | (widget.width * 0.306) + 60 | (-10.0 + (20.0 * _animationController.value)), 61 | 0.0, 62 | ), 63 | child: Image( 64 | image: AssetImage( 65 | "assets/images/Cloud.png", 66 | ), 67 | fit: BoxFit.scaleDown, 68 | 69 | //color: Colors.black, 70 | ), 71 | ), 72 | ), 73 | ); 74 | }) 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_back_preview.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class BookmarkBackPreview extends StatelessWidget { 4 | final EdgeInsets padding; 5 | final bool transformed; 6 | final UserData? userData; 7 | final AnimationController? animationController; 8 | const BookmarkBackPreview({ 9 | Key? key, 10 | required this.userData, 11 | this.padding = const EdgeInsets.only( 12 | left: 16.0, 13 | right: 16.0, 14 | top: 30.0, 15 | bottom: 30.0, 16 | ), 17 | this.transformed = true, 18 | this.animationController, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return BookmarkPreviewContainer( 24 | transformed: transformed, 25 | reveredAnimation: true, 26 | animationController: animationController, 27 | padding: padding, 28 | child: BookmarkBack( 29 | userData: userData, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_container.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class BookmarkFittedBox extends StatelessWidget { 4 | final double maxWidth; 5 | final Widget child; 6 | final BoxDecoration boxDecoration; 7 | const BookmarkFittedBox({ 8 | Key? key, 9 | required this.maxWidth, 10 | required this.child, 11 | this.boxDecoration = const BoxDecoration( 12 | boxShadow: [ 13 | BoxShadow( 14 | color: Colors.black26, 15 | offset: Offset(-3.0, 3.0), 16 | blurRadius: 8.0, 17 | spreadRadius: 1.0, 18 | ) 19 | ], 20 | ), 21 | }) : super(key: key); 22 | @override 23 | Widget build(BuildContext context) { 24 | return SizedBox( 25 | width: maxWidth, 26 | child: AspectRatio( 27 | aspectRatio: BookmarkCover.aspectRatio, 28 | child: Container( 29 | decoration: boxDecoration, 30 | child: child, 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_cover.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | abstract class BookmarkCover extends Widget { 4 | static const Size dimensions = const Size(8.5, 2.5); 5 | static double get aspectRatio => dimensions.aspectRatio; 6 | static double get height => dimensions.height; 7 | static double get width => dimensions.width; 8 | } 9 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_design.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | abstract class BookmarkDesign extends Widget {} 4 | 5 | enum DesignType { 6 | stiped, 7 | dotted, 8 | } 9 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_front.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class BookmarkFront extends StatelessWidget implements BookmarkCover { 4 | final UserData userData; 5 | final double maxWidth; 6 | final double radius; 7 | const BookmarkFront({ 8 | Key? key, 9 | required this.userData, 10 | this.maxWidth = 400.0, 11 | this.radius = 6.0, 12 | }) : super(key: key); 13 | @override 14 | Widget build(BuildContext context) { 15 | return BookmarkFittedBox( 16 | maxWidth: maxWidth, 17 | child: Stack( 18 | children: [ 19 | _BookmarkFrontArt( 20 | userData: userData, 21 | radius: radius, 22 | ), 23 | BookmarkTitle(userData: userData), 24 | ], 25 | ), 26 | ); 27 | } 28 | } 29 | 30 | class _BookmarkFrontArt extends StatelessWidget { 31 | final UserData userData; 32 | final double radius; 33 | const _BookmarkFrontArt({ 34 | Key? key, 35 | required this.userData, 36 | required this.radius, 37 | }) : super(key: key); 38 | 39 | /// Returns the current design type selected by the user. 40 | DesignType get type => DesignType.values[userData.designPatternIndex]; 41 | 42 | /// Returns the appropriate design implementation based on the [type] value. 43 | Widget get design { 44 | switch (type) { 45 | case DesignType.stiped: 46 | return StripedDesign( 47 | radius: radius, 48 | userData: userData, 49 | ); 50 | case DesignType.dotted: 51 | return DottedDesign( 52 | radius: radius, 53 | userData: userData, 54 | ); 55 | default: 56 | return StripedDesign( 57 | radius: radius, 58 | userData: userData, 59 | ); 60 | } 61 | } 62 | 63 | Widget build(BuildContext context) { 64 | return Stack( 65 | children: [ 66 | Container( 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.all( 69 | Radius.circular( 70 | radius, 71 | ), 72 | ), 73 | ), 74 | ), 75 | design 76 | ], 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_front_preview.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class BookmarkFrontPreview extends StatelessWidget { 4 | final EdgeInsets padding; 5 | final bool transformed; 6 | final UserData userData; 7 | final AnimationController? animationController; 8 | const BookmarkFrontPreview({ 9 | Key? key, 10 | required this.userData, 11 | this.padding = const EdgeInsets.only( 12 | left: 16.0, 13 | right: 16.0, 14 | top: 30.0, 15 | bottom: 30.0, 16 | ), 17 | this.transformed = true, 18 | this.animationController, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return BookmarkPreviewContainer( 24 | transformed: transformed, 25 | animationController: animationController, 26 | padding: padding, 27 | child: BookmarkFront( 28 | userData: userData, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_page_view.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A History of Me widget displaying the bookmark's front and back on a page 4 | /// view. 5 | class BookmarkPageView extends StatelessWidget { 6 | /// The data containing the bookmark configuration. 7 | final UserData userData; 8 | 9 | /// The bookmark's animation. 10 | final AnimationController animationController; 11 | 12 | /// The page view's total height. 13 | final double height; 14 | 15 | final EdgeInsets padding; 16 | 17 | /// Creates a [BookmarkPageView]. 18 | const BookmarkPageView({ 19 | Key? key, 20 | required this.userData, 21 | required this.animationController, 22 | this.height = 180.0, 23 | this.padding = LitEdgeInsets.screen, 24 | }) : super(key: key); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return IndexedPageView( 29 | height: height, 30 | indicatorSpacingTop: 0.0, 31 | tapToNavigate: true, 32 | children: [ 33 | BookmarkFrontPreview( 34 | userData: userData, 35 | animationController: animationController, 36 | padding: padding, 37 | ), 38 | BookmarkBackPreview( 39 | userData: userData, 40 | animationController: animationController, 41 | padding: padding, 42 | ), 43 | ], 44 | indicatorColor: LitColors.grey400, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_preview_container.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class BookmarkPreviewContainer extends StatefulWidget { 4 | final bool transformed; 5 | final bool reveredAnimation; 6 | final EdgeInsets padding; 7 | final BookmarkCover child; 8 | final AnimationController? animationController; 9 | const BookmarkPreviewContainer({ 10 | Key? key, 11 | required this.transformed, 12 | this.reveredAnimation = false, 13 | required this.padding, 14 | required this.child, 15 | this.animationController, 16 | }) : super(key: key); 17 | @override 18 | _BookmarkPreviewContainerState createState() => 19 | _BookmarkPreviewContainerState(); 20 | } 21 | 22 | class _BookmarkPreviewContainerState extends State 23 | with TickerProviderStateMixin { 24 | //late AnimationController _animationController; 25 | 26 | // @override 27 | // void initState() { 28 | // super.initState(); 29 | // _animationController = AnimationController( 30 | // duration: Duration(milliseconds: 5000), 31 | // vsync: this, 32 | // ); 33 | // if (widget.transformed) { 34 | // _animationController.repeat(reverse: true); 35 | // } 36 | // } 37 | 38 | // @override 39 | // void dispose() { 40 | // _animationController.dispose(); 41 | // super.dispose(); 42 | // } 43 | // 44 | // 45 | 46 | double get _animationValue { 47 | return widget.reveredAnimation 48 | ? (1.0 - widget.animationController!.value) 49 | : (widget.animationController!.value); 50 | } 51 | 52 | double get _rotation { 53 | return (2 * pi / 360) * 3 * (_animationValue); 54 | } 55 | 56 | Matrix4 get _transform { 57 | return Matrix4.translationValues( 58 | -20 + (20 * _animationValue), -20 + (20 * _animationValue), 0); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Padding( 64 | padding: widget.padding, 65 | child: widget.animationController != null 66 | ? AnimatedBuilder( 67 | animation: widget.animationController!, 68 | builder: (context, _) { 69 | return Transform.rotate( 70 | angle: _rotation, 71 | child: Transform( 72 | transform: widget.transformed 73 | ? _transform 74 | : Matrix4.translationValues(0, 0, 0), 75 | child: _Bookmark( 76 | child: widget.child, 77 | ), 78 | //UserBookmark(userData: ud) 79 | ), 80 | ); 81 | }) 82 | : _Bookmark( 83 | child: widget.child, 84 | ), 85 | ); 86 | } 87 | } 88 | 89 | class _Bookmark extends StatelessWidget { 90 | final Widget child; 91 | 92 | const _Bookmark({ 93 | Key? key, 94 | required this.child, 95 | }) : super(key: key); 96 | @override 97 | Widget build(BuildContext context) { 98 | return Stack(alignment: Alignment.center, children: [child]); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/widgets/bookmark_title.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A History of Me widget displaying the bookmark's title, which is derived 4 | /// by the provided username. 5 | class BookmarkTitle extends StatelessWidget { 6 | /// Creates a [BookmarkTitle]. 7 | 8 | /// The [UserData] containing the username. 9 | final UserData userData; 10 | 11 | /// Specifies the surrounding [BorderRadius]. 12 | final BorderRadiusGeometry borderRadius; 13 | 14 | /// Specifies the alignment on the bookmark. 15 | final Alignment alignment; 16 | 17 | const BookmarkTitle({ 18 | Key? key, 19 | required this.userData, 20 | this.borderRadius = const BorderRadius.only( 21 | topLeft: Radius.circular(6.0), 22 | bottomLeft: Radius.circular(6.0), 23 | ), 24 | this.alignment = Alignment.centerLeft, 25 | }) : super(key: key); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return LayoutBuilder( 30 | builder: (context, constraints) { 31 | return Align( 32 | alignment: alignment, 33 | child: AspectRatio( 34 | aspectRatio: 3 / 4, 35 | child: Container( 36 | decoration: BoxDecoration( 37 | color: LitColors.grey120, 38 | borderRadius: borderRadius, 39 | ), 40 | child: Stack( 41 | children: [ 42 | _KeyIcon(constraints: constraints), 43 | _HistoryOfLabel(userData: userData), 44 | ], 45 | ), 46 | ), 47 | ), 48 | ); 49 | }, 50 | ); 51 | } 52 | } 53 | 54 | /// A History of Me widget displaying the `History Of Me`'s key icon. 55 | class _KeyIcon extends StatelessWidget { 56 | final BoxConstraints constraints; 57 | 58 | /// Creates a [_KeyIcon]. 59 | const _KeyIcon({ 60 | Key? key, 61 | required this.constraints, 62 | }) : super(key: key); 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return Padding( 67 | padding: const EdgeInsets.only( 68 | left: 4.0, 69 | ), 70 | child: Align( 71 | alignment: Alignment.center, 72 | child: Image( 73 | color: LitColors.grey150, 74 | image: AssetImage( 75 | AppAssets.keyLogo64px, 76 | ), 77 | fit: BoxFit.fitHeight, 78 | //color: Colors.black, 79 | height: constraints.maxHeight * 0.8, 80 | ), 81 | ), 82 | ); 83 | } 84 | } 85 | 86 | /// A History of Me widget displaying a stylized `History of` label. 87 | class _HistoryOfLabel extends StatelessWidget { 88 | final UserData userData; 89 | 90 | /// Creates a [_HistoryOfLabel]. 91 | const _HistoryOfLabel({ 92 | Key? key, 93 | required this.userData, 94 | }) : super(key: key); 95 | 96 | /// A constant string stating `History` due to it being the same on all 97 | /// locales. 98 | static const historyLabel = "History"; 99 | 100 | /// A constant string stating `of` due to it being the same on all 101 | /// locales. 102 | static const ofLabel = "of"; 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | return RotatedBox( 107 | quarterTurns: 1, 108 | child: Align( 109 | alignment: Alignment.centerRight, 110 | child: Column( 111 | mainAxisSize: MainAxisSize.min, 112 | children: [ 113 | Row( 114 | mainAxisAlignment: MainAxisAlignment.center, 115 | children: [ 116 | RotatedBox( 117 | quarterTurns: 3, 118 | child: ScaledDownText( 119 | ofLabel, 120 | style: LitSerifStyles.caption.copyWith( 121 | fontSize: 8.0, 122 | ), 123 | textAlign: TextAlign.center, 124 | ), 125 | ), 126 | ScaledDownText( 127 | historyLabel, 128 | style: LitSerifStyles.caption.copyWith( 129 | fontSize: 8.0, 130 | ), 131 | textAlign: TextAlign.center, 132 | ), 133 | ], 134 | ), 135 | Padding( 136 | padding: const EdgeInsets.symmetric( 137 | horizontal: 8.0, 138 | ), 139 | child: ClippedText( 140 | userData.name, 141 | style: LitSerifStyles.subtitle2.copyWith( 142 | fontSize: 11.0, 143 | ), 144 | textAlign: TextAlign.center, 145 | ), 146 | ), 147 | ], 148 | ), 149 | ), 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /lib/widgets/cancel_restoring_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A Flutter widget allowing the user to confirm the creation of a new diary 4 | /// while canceling the restoring of a backuped diary. 5 | /// 6 | class CancelRestoringDialog extends StatelessWidget { 7 | /// Handles the `cancel` action. 8 | final void Function() onCancel; 9 | 10 | /// Creates a [CancelRestoringDialog]. 11 | const CancelRestoringDialog({ 12 | Key? key, 13 | required this.onCancel, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return LitTitledDialog( 19 | child: LitDescriptionTextBox( 20 | padding: const EdgeInsets.symmetric( 21 | horizontal: 16.0, 22 | vertical: 8.0, 23 | ), 24 | text: AppLocalizations.of(context).cancelRestoreDescr, 25 | ), 26 | titleText: LeitmotifLocalizations.of(context).cancelLabel + "?", 27 | actionButtons: [ 28 | Padding( 29 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 30 | child: LitPushedThroughButton( 31 | child: Text( 32 | AppLocalizations.of(context).createLabel.toUpperCase(), 33 | style: LitSansSerifStyles.button, 34 | textAlign: TextAlign.center, 35 | ), 36 | onPressed: onCancel, 37 | ), 38 | ) 39 | ], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/widgets/clean_text_field.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A clean [TextField] without border decoration applied. 4 | /// 5 | /// Allows to provide a custom text style. 6 | class CleanTextField extends StatelessWidget { 7 | final FocusNode focusNode; 8 | final TextEditingController controller; 9 | final TextStyle style; 10 | final bool showCounter; 11 | final int? maxLines; 12 | final int? minLines; 13 | 14 | /// Creates a [CleanTextField]. 15 | const CleanTextField({ 16 | Key? key, 17 | required this.controller, 18 | required this.focusNode, 19 | required this.style, 20 | this.maxLines, 21 | this.minLines, 22 | this.showCounter = false, 23 | }) : super(key: key); 24 | 25 | /// Returns the hint's text style 26 | TextStyle get hintStyle => style.copyWith(color: LitColors.grey200); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return TextField( 31 | controller: controller, 32 | cursorColor: LitColors.grey200, 33 | cursorRadius: Radius.circular(2.0), 34 | decoration: InputDecoration( 35 | border: InputBorder.none, 36 | counter: showCounter ? WordCountBadge(controller: controller) : null, 37 | hintText: AppLocalizations.of(context).hintText + "...", 38 | hintStyle: hintStyle, 39 | ), 40 | focusNode: focusNode, 41 | maxLines: maxLines, 42 | minLines: minLines, 43 | style: style, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/widgets/create_new_diary_action_card.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A Flutter widget displaying an action card to allow the user to create a 4 | /// new diary. 5 | class CreateNewActionCard extends StatelessWidget { 6 | /// Handles the `create` action. 7 | final void Function() onCreate; 8 | 9 | /// Creates a [CreateNewActionCard]. 10 | const CreateNewActionCard({ 11 | Key? key, 12 | required this.onCreate, 13 | }) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return LitTitledActionCard( 18 | title: AppLocalizations.of(context).newDiaryTitle, 19 | subtitle: AppLocalizations.of(context).startJourneyTitle, 20 | actionButtonData: [ 21 | ActionButtonData( 22 | title: AppLocalizations.of(context).createLabel, 23 | onPressed: onCreate, 24 | backgroundColor: AppColors.pastelPink, 25 | accentColor: AppColors.pastelPurple, 26 | ), 27 | ], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/widgets/delete_all_photos_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A dialog widgets allowing to confirm the deletion of all photos related to 4 | /// a diary entry. The corresponding diary entry's photo list will be updated 5 | /// using an empty list. 6 | class DeleteAllPhotosDialog extends StatefulWidget { 7 | /// The diary entry, whose photos should be deleted. 8 | final DiaryEntry entry; 9 | 10 | /// Creates a [DeleteAllPhotosDialog]. 11 | const DeleteAllPhotosDialog({ 12 | Key? key, 13 | required this.entry, 14 | }) : super(key: key); 15 | 16 | @override 17 | State createState() => _DeleteAllPhotosDialogState(); 18 | } 19 | 20 | class _DeleteAllPhotosDialogState extends State { 21 | /// The [AppAPI] instance. 22 | final AppAPI _api = AppAPI(); 23 | 24 | /// Deletes the photos and closes the dialog. 25 | void _deletePhotos() { 26 | DiaryEntry updated = DiaryEntry( 27 | uid: widget.entry.uid, 28 | date: widget.entry.date, 29 | created: widget.entry.created, 30 | lastUpdated: widget.entry.lastUpdated, 31 | title: widget.entry.title, 32 | content: widget.entry.content, 33 | moodScore: widget.entry.moodScore, 34 | favorite: widget.entry.favorite, 35 | backdropPhotoId: widget.entry.backdropPhotoId, 36 | photos: DefaultData.photos, 37 | visitCount: widget.entry.visitCount, 38 | editCount: widget.entry.editCount, 39 | ); 40 | // Update the diary entry to delete all photos. 41 | _api.updateDiaryEntry(updated); 42 | Navigator.of(context).pop(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return LitTitledDialog( 48 | titleText: AppLocalizations.of(context).noPhotosSelectedLabel, 49 | margin: LitEdgeInsets.dialogMargin, 50 | child: Column( 51 | children: [ 52 | LitDescriptionTextBox( 53 | text: AppLocalizations.of(context).deletePhotosDescr, 54 | ), 55 | SizedBox(height: 4.0), 56 | Text( 57 | AppLocalizations.of(context).deletePhotosActionLabel, 58 | style: LitSansSerifStyles.subtitle2, 59 | ), 60 | ], 61 | ), 62 | actionButtons: [ 63 | DialogActionButton( 64 | data: ActionButtonData( 65 | title: LeitmotifLocalizations.of(context).deleteLabel, 66 | backgroundColor: LitColors.red200, 67 | accentColor: LitColors.red200, 68 | onPressed: _deletePhotos, 69 | ), 70 | ) 71 | ], 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/widgets/diary_bookmark_header.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class DiaryBookmarkHeader extends StatelessWidget { 4 | final UserData userData; 5 | final AnimationController bookmarkAnimation; 6 | const DiaryBookmarkHeader({ 7 | Key? key, 8 | required this.userData, 9 | required this.bookmarkAnimation, 10 | }) : super(key: key); 11 | @override 12 | Widget build(BuildContext context) { 13 | return SliverList( 14 | delegate: SliverChildBuilderDelegate( 15 | (BuildContext context, int index) { 16 | return Column( 17 | children: [ 18 | Container( 19 | color: Colors.white, 20 | child: Stack( 21 | children: [ 22 | BookmarkPageView( 23 | animationController: bookmarkAnimation, 24 | userData: userData, 25 | padding: const EdgeInsets.only( 26 | top: 0, 27 | bottom: 32.0, 28 | left: 16.0, 29 | right: 16.0, 30 | ), 31 | ), 32 | ], 33 | ), 34 | ) 35 | ], 36 | ); 37 | }, 38 | childCount: 1, 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/widgets/diary_entry_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class DiaryEntryBottomSheet extends StatelessWidget { 4 | final String? title; 5 | final void Function() onPressedEdit; 6 | final void Function() onPressedDelete; 7 | const DiaryEntryBottomSheet({ 8 | Key? key, 9 | this.title, 10 | required this.onPressedEdit, 11 | required this.onPressedDelete, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Padding( 17 | padding: const EdgeInsets.symmetric( 18 | horizontal: 16.0, 19 | ), 20 | child: Column( 21 | mainAxisSize: MainAxisSize.min, 22 | children: [ 23 | SizedBox(height: 16.0), 24 | ScrollableText( 25 | title ?? AppLocalizations.of(context).optionsLabel.capitalize(), 26 | style: LitSansSerifStyles.h6.copyWith( 27 | color: LitColors.grey380, 28 | ), 29 | ), 30 | Divider(), 31 | SizedBox( 32 | height: 142.0, 33 | child: ListView( 34 | padding: const EdgeInsets.symmetric( 35 | vertical: 16.0, 36 | ), 37 | children: [ 38 | LitPushedThroughButton( 39 | child: Row( 40 | mainAxisAlignment: MainAxisAlignment.center, 41 | children: [ 42 | Icon( 43 | LitIcons.pencil_alt, 44 | color: LitSansSerifStyles.button.color, 45 | size: LitSansSerifStyles.button.fontSize, 46 | ), 47 | SizedBox(width: 8.0), 48 | ClippedText( 49 | AppLocalizations.of(context).editLabel.toUpperCase(), 50 | textAlign: TextAlign.center, 51 | style: LitSansSerifStyles.button, 52 | ), 53 | ], 54 | ), 55 | accentColor: LitColors.grey100, 56 | onPressed: onPressedEdit, 57 | ), 58 | SizedBox(height: 16.0), 59 | LitDeleteButton( 60 | textAlign: TextAlign.center, 61 | showIcon: true, 62 | onPressed: onPressedDelete, 63 | ) 64 | ], 65 | ), 66 | ) 67 | ], 68 | ), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/widgets/diary_filter_header_delegate.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class DiaryFilterHeaderDelegate extends SliverPersistentHeaderDelegate { 4 | DiaryFilterHeaderDelegate( 5 | this.child, 6 | ); 7 | final Widget child; 8 | 9 | @override 10 | Widget build( 11 | BuildContext context, double shrinkOffset, bool overlapsContent) { 12 | return child; 13 | } 14 | 15 | @override 16 | double get maxExtent => 52.0; 17 | 18 | @override 19 | double get minExtent => 52.0; 20 | 21 | @override 22 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) { 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/widgets/editable_item_meta_info.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class EditableItemMetaInfo extends StatefulWidget { 4 | final int? lastUpdateTimestamp; 5 | final bool showUnsavedBadge; 6 | 7 | const EditableItemMetaInfo({ 8 | Key? key, 9 | required this.lastUpdateTimestamp, 10 | required this.showUnsavedBadge, 11 | }) : super(key: key); 12 | 13 | @override 14 | _EditableItemMetaInfoState createState() => _EditableItemMetaInfoState(); 15 | } 16 | 17 | class _EditableItemMetaInfoState extends State 18 | with TickerProviderStateMixin { 19 | AnimationController? _animationController; 20 | 21 | @override 22 | void initState() { 23 | _animationController = AnimationController( 24 | duration: Duration( 25 | milliseconds: 3000, 26 | ), 27 | vsync: this, 28 | ); 29 | _animationController!.repeat(reverse: true); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _animationController!.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Row( 42 | mainAxisAlignment: MainAxisAlignment.end, 43 | children: [ 44 | widget.showUnsavedBadge 45 | ? _AnimatedUnchangedBadge( 46 | animation: _animationController, 47 | ) 48 | : SizedBox(), 49 | AnimatedUpdatedLabel( 50 | lastUpdateTimestamp: widget.lastUpdateTimestamp, 51 | ), 52 | ], 53 | ); 54 | } 55 | } 56 | 57 | class _AnimatedUnchangedBadge extends StatelessWidget { 58 | final Animation? animation; 59 | 60 | const _AnimatedUnchangedBadge({ 61 | Key? key, 62 | required this.animation, 63 | }) : super(key: key); 64 | @override 65 | Widget build(BuildContext context) { 66 | return ConstrainedBox( 67 | constraints: BoxConstraints( 68 | maxWidth: 86.0, 69 | ), 70 | child: AnimatedBuilder( 71 | animation: animation!, 72 | builder: (context, _) { 73 | return LitTextBadge( 74 | backgroundColor: Color.lerp(LitColors.mediumGrey, Colors.white, 75 | 0.8 * animation!.value) ?? 76 | LitColors.mediumGrey, 77 | label: LeitmotifLocalizations.of(context).unsavedLabel.capitalize(), 78 | textColor: Color.lerp( 79 | Colors.white, 80 | LitColors.mediumGrey, 81 | 0.1 + (animation!.value * 0.9), 82 | ) ?? 83 | Colors.white, 84 | ); 85 | }, 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/widgets/ellipse_icon.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class EllipseIcon extends StatefulWidget { 4 | final bool animated; 5 | final Axis axis; 6 | final double dotHeight; 7 | final double dotWidth; 8 | final Color dotColor; 9 | final List boxShadow; 10 | 11 | const EllipseIcon( 12 | {Key? key, 13 | this.animated = true, 14 | this.axis = Axis.horizontal, 15 | this.dotHeight = 8.0, 16 | this.dotWidth = 8.0, 17 | this.dotColor = LitColors.mediumGrey, 18 | this.boxShadow = const [ 19 | BoxShadow( 20 | blurRadius: 3.0, 21 | color: Colors.black12, 22 | offset: Offset( 23 | 1.0, 24 | 1.0, 25 | ), 26 | spreadRadius: 1.0, 27 | ) 28 | ]}) 29 | : super(key: key); 30 | 31 | @override 32 | _EllipsisIconState createState() => _EllipsisIconState(); 33 | } 34 | 35 | class _EllipsisIconState extends State 36 | with TickerProviderStateMixin { 37 | AnimationController? _animationController; 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | _animationController = AnimationController( 43 | duration: Duration( 44 | milliseconds: 590, 45 | ), 46 | vsync: this, 47 | ); 48 | if (widget.animated) { 49 | _animationController!.forward(); 50 | } 51 | } 52 | 53 | @override 54 | void dispose() { 55 | _animationController!.dispose(); 56 | super.dispose(); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return AnimatedBuilder( 62 | animation: _animationController!, 63 | builder: (context, _) { 64 | final List children = []; 65 | for (int i = 0; i < 3; i++) { 66 | children.add( 67 | _Dot( 68 | animated: widget.animated, 69 | axis: widget.axis, 70 | animationController: _animationController, 71 | height: widget.dotHeight, 72 | width: widget.dotWidth, 73 | color: widget.dotColor, 74 | boxShadow: widget.boxShadow, 75 | ), 76 | ); 77 | } 78 | return widget.axis == Axis.horizontal 79 | ? Row( 80 | mainAxisSize: MainAxisSize.min, 81 | children: children, 82 | ) 83 | : Column( 84 | mainAxisSize: MainAxisSize.min, 85 | children: children, 86 | ); 87 | }, 88 | ); 89 | } 90 | } 91 | 92 | class _Dot extends StatelessWidget { 93 | final AnimationController? animationController; 94 | final bool animated; 95 | final double height; 96 | final double width; 97 | final Color color; 98 | final List boxShadow; 99 | final Axis axis; 100 | const _Dot({ 101 | Key? key, 102 | required this.animationController, 103 | required this.animated, 104 | required this.height, 105 | required this.width, 106 | required this.color, 107 | required this.boxShadow, 108 | required this.axis, 109 | }) : super(key: key); 110 | @override 111 | Widget build(BuildContext context) { 112 | return Transform( 113 | transform: animated 114 | ? Matrix4.translationValues( 115 | -8.0 + 8.0 * (animationController!.value), 116 | 0, 117 | 0, 118 | ) 119 | : Matrix4.translationValues( 120 | 0, 121 | 0, 122 | 0, 123 | ), 124 | child: Padding( 125 | padding: const EdgeInsets.symmetric( 126 | horizontal: 2.0, 127 | vertical: 1.0, 128 | ), 129 | child: Container( 130 | height: height, 131 | width: width, 132 | decoration: BoxDecoration( 133 | borderRadius: BorderRadius.circular(4), 134 | color: color, 135 | boxShadow: boxShadow, 136 | ), 137 | ), 138 | ), 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/widgets/greetings_bar.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class GreetingsBar extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | width: MediaQuery.of(context).size.width, 8 | height: Scaffold.of(context).appBarMaxHeight, 9 | decoration: BoxDecoration( 10 | color: Colors.white, 11 | boxShadow: [ 12 | BoxShadow( 13 | blurRadius: 8.0, 14 | offset: Offset(2, 4), 15 | color: Colors.black, 16 | spreadRadius: 2.0, 17 | ) 18 | ], 19 | ), 20 | child: Padding( 21 | padding: const EdgeInsets.symmetric( 22 | vertical: 16.0, 23 | ), 24 | child: Text( 25 | AppLocalizations.of(context).greetingLabel, 26 | textAlign: TextAlign.center, 27 | style: LitSansSerifStyles.subtitle2.copyWith( 28 | fontWeight: FontWeight.bold, 29 | ), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/history_of_me_app_logo.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class HistoryOfMeAppLogo extends StatelessWidget { 4 | final double width; 5 | final double height; 6 | final bool showKeyImage; 7 | final Color color; 8 | const HistoryOfMeAppLogo( 9 | {Key? key, 10 | this.width = 100.0, 11 | this.height = 120.0, 12 | this.showKeyImage = false, 13 | this.color = const Color(0xFFadadad)}) 14 | : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return SizedBox( 19 | width: width, 20 | height: height, 21 | child: FittedBox( 22 | fit: BoxFit.scaleDown, 23 | child: Container( 24 | child: Stack( 25 | children: [ 26 | Column( 27 | children: [ 28 | RotatedBox( 29 | quarterTurns: 3, 30 | child: Text( 31 | "History", 32 | style: TextStyle( 33 | fontFamily: "Playfair Display", 34 | color: color, 35 | fontSize: 24.0, 36 | letterSpacing: 0.0, 37 | fontWeight: FontWeight.w300, 38 | ), 39 | ), 40 | ), 41 | Text( 42 | "of", 43 | style: TextStyle( 44 | fontFamily: "Playfair Display", 45 | color: color, 46 | fontSize: 20.0, 47 | letterSpacing: -0.65, 48 | fontWeight: FontWeight.w500, 49 | ), 50 | ), 51 | ], 52 | ), 53 | Transform( 54 | transform: Matrix4.translationValues(12, 27.5, 0), 55 | child: RotatedBox( 56 | quarterTurns: 3, 57 | child: Text( 58 | "me", 59 | style: LitTextStyles.sansSerif.copyWith( 60 | color: color, 61 | fontSize: 52.0, 62 | letterSpacing: -4.75, 63 | fontWeight: FontWeight.w500, 64 | ), 65 | ), 66 | ), 67 | ), 68 | showKeyImage 69 | ? Transform( 70 | transform: Matrix4.translationValues(58, 57.5, 0), 71 | child: Padding( 72 | padding: const EdgeInsets.symmetric( 73 | horizontal: 2.0, 74 | ), 75 | child: Image( 76 | image: AssetImage( 77 | "assets/images/History_Of_Me_Key_Icon_256px-01.png", 78 | ), 79 | // fit: BoxFit.fitWidth, 80 | color: HexColor('#c9b5b5'), 81 | //color: Colors.black, 82 | height: 46.0, 83 | ), 84 | ), 85 | ) 86 | : SizedBox(), 87 | ], 88 | ), 89 | ), 90 | ), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/widgets/image_preview_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A dialog `widget` displaying a preview of the provided image's path. 4 | class ImagePreviewDialog extends StatefulWidget { 5 | /// The images's path. 6 | final String path; 7 | 8 | /// Creates a [ImagePreviewDialog]. 9 | const ImagePreviewDialog({ 10 | Key? key, 11 | required this.path, 12 | }) : super(key: key); 13 | 14 | @override 15 | State createState() => _ImagePreviewDialogState(); 16 | } 17 | 18 | class _ImagePreviewDialogState extends State { 19 | /// Pops the dialog from the navigation stack. 20 | void _onTap() { 21 | Navigator.of(context).pop(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Dialog( 27 | backgroundColor: Colors.black38, 28 | insetPadding: EdgeInsets.all(8.0), 29 | child: InkWell( 30 | onTap: _onTap, 31 | child: Container( 32 | child: Image.file( 33 | File( 34 | widget.path, 35 | ), 36 | fit: BoxFit.fitWidth, 37 | ), 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/widgets/launcher_icon_art.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class AppLauncherIconArt extends StatelessWidget { 4 | final double height; 5 | final double width; 6 | final List boxShadow; 7 | const AppLauncherIconArt({ 8 | Key? key, 9 | this.height = 96.0, 10 | this.width = 96.0, 11 | this.boxShadow = const [], 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return SizedBox( 17 | child: Container( 18 | height: height, 19 | width: width, 20 | decoration: BoxDecoration( 21 | borderRadius: BorderRadius.all( 22 | Radius.circular(height / 3), 23 | ), 24 | color: Color(0xffffefe1), 25 | boxShadow: boxShadow, 26 | ), 27 | child: Image( 28 | image: AssetImage( 29 | AppAssets.appIconAdaptive, 30 | ), 31 | fit: BoxFit.scaleDown, 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/widgets/pattern_config_card.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class PatternConfigCard extends StatefulWidget { 4 | final int designPattern; 5 | //final String patternLabel; 6 | final int? patternValue; 7 | final void Function(double) onPatternSliderChange; 8 | final int min; 9 | final int max; 10 | const PatternConfigCard({ 11 | Key? key, 12 | required this.designPattern, 13 | //required this.patternLabel, 14 | required this.patternValue, 15 | required this.onPatternSliderChange, 16 | required this.min, 17 | required this.max, 18 | }) : super(key: key); 19 | 20 | @override 21 | _PatternConfigCardState createState() => _PatternConfigCardState(); 22 | } 23 | 24 | class _PatternConfigCardState extends State { 25 | bool get _showMetaData { 26 | return widget.designPattern == 0; 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Padding( 32 | padding: EdgeInsets.symmetric( 33 | horizontal: 16.0, 34 | vertical: 8.0, 35 | ), 36 | child: LitTitledActionCard( 37 | child: LitSlider( 38 | displayValue: _showMetaData, 39 | displayRangeBadges: _showMetaData, 40 | max: widget.max.toDouble(), 41 | min: widget.min.toDouble(), 42 | onChanged: widget.onPatternSliderChange, 43 | value: widget.patternValue!.toDouble(), 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/photos_missing_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A dialog widgets shown in case some photos cannot be restored from the 4 | /// backup directory. 5 | class PhotosMissingDialog extends StatelessWidget { 6 | /// Creates a [PhotosMissingDialog]. 7 | const PhotosMissingDialog({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return LitTitledDialog( 12 | titleText: AppLocalizations.of(context).photosMissingLabel, 13 | margin: const EdgeInsets.symmetric( 14 | vertical: 8.0, 15 | horizontal: 16.0, 16 | ), 17 | minHeight: 128.0, 18 | child: LitDescriptionTextBox( 19 | maxLines: 6, 20 | text: AppLocalizations.of(context).photosMissingDescr, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/widgets/pick_photos_button.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A button widget allowing to pick image files. 4 | class PickPhotosButton extends StatelessWidget { 5 | final void Function() onPressed; 6 | 7 | /// Creates a [PickPhotosButton]. 8 | const PickPhotosButton({ 9 | Key? key, 10 | required this.onPressed, 11 | }) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return LitPushedThroughButton( 16 | child: Row( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | Icon( 20 | LitIcons.photos, 21 | color: LitColors.grey500, 22 | size: 16.0, 23 | ), 24 | SizedBox(width: 8.0), 25 | Text( 26 | AppLocalizations.of(context).pickPhotoLabel.toUpperCase(), 27 | style: LitSansSerifStyles.button, 28 | ), 29 | ], 30 | ), 31 | onPressed: onPressed, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/purple_pink_button.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A Flutter widget displaying a gradient-decorated button. 4 | class PurplePinkButton extends StatelessWidget { 5 | /// The button's label. 6 | final String label; 7 | 8 | /// Handles the `onPressed` action. 9 | final void Function() onPressed; 10 | 11 | /// Creates a [PurplePinkButton]. 12 | const PurplePinkButton({ 13 | Key? key, 14 | required this.label, 15 | required this.onPressed, 16 | }) : super(key: key); 17 | @override 18 | Widget build(BuildContext context) { 19 | return LitGradientButton( 20 | accentColor: AppColors.purple, 21 | color: AppColors.pink, 22 | child: Text( 23 | label.toUpperCase(), 24 | style: LitTextStyles.sansSerifStyles[button].copyWith( 25 | color: Colors.white, 26 | fontSize: 13.0, 27 | fontWeight: FontWeight.w700, 28 | letterSpacing: 1.3, 29 | ), 30 | ), 31 | onPressed: onPressed, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/widgets/purple_pink_save_button.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A draggable purple pink button showing a [LitIcons.disk] icon as button 4 | /// label. 5 | /// 6 | /// It is designed to be used for saving changes. 7 | class PurplePinkSaveButton extends StatelessWidget { 8 | final bool disabled; 9 | final void Function() onSaveChanges; 10 | 11 | /// Creates a [PurplePinkSaveButton]. 12 | /// 13 | /// * [disabled] states whether to hide the button (e.g. if no changes have 14 | /// been made). 15 | /// 16 | /// * [onSaveChanges] is the 'save' callback triggering the process to write 17 | /// to the database. 18 | const PurplePinkSaveButton({ 19 | Key? key, 20 | required this.disabled, 21 | required this.onSaveChanges, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return !disabled 27 | ? LitDraggable( 28 | initialDragOffset: Offset( 29 | MediaQuery.of(context).size.width - 90.0, 30 | MediaQuery.of(context).size.height - 156.0, 31 | ), 32 | child: LitGradientButton( 33 | accentColor: const Color(0xFFDE8FFA), 34 | color: const Color(0xFFFA72AA), 35 | child: Icon( 36 | LitIcons.disk, 37 | size: 28.0, 38 | color: Colors.white, 39 | ), 40 | onPressed: onSaveChanges, 41 | ), 42 | ) 43 | : SizedBox(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/widgets/quote_card.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class QuoteCard extends StatefulWidget { 4 | final TextEditingController authorController; 5 | final TextEditingController quoteController; 6 | const QuoteCard({ 7 | Key? key, 8 | required this.authorController, 9 | required this.quoteController, 10 | }) : super(key: key); 11 | 12 | @override 13 | _QuoteCardState createState() => _QuoteCardState(); 14 | } 15 | 16 | class _QuoteCardState extends State { 17 | final FocusNode _quoteFocus = FocusNode(); 18 | final FocusNode _authorFocus = FocusNode(); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Padding( 23 | padding: EdgeInsets.symmetric( 24 | horizontal: 16.0, 25 | vertical: 8.0, 26 | ), 27 | child: LitTitledActionCard( 28 | title: AppLocalizations.of(context).quoteLabel.capitalize(), 29 | subtitle: AppLocalizations.of(context).quoteSubtitle, 30 | child: Column( 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | ConstrainedBox( 34 | constraints: BoxConstraints(minHeight: 78.0), 35 | child: Padding( 36 | padding: const EdgeInsets.symmetric( 37 | vertical: 8.0, 38 | ), 39 | child: CleanTextField( 40 | focusNode: _quoteFocus, 41 | style: LitTextStyles.sansSerif.copyWith( 42 | height: 1.5, 43 | letterSpacing: 0.32, 44 | ), 45 | controller: widget.quoteController, 46 | ), 47 | ), 48 | ), 49 | Padding( 50 | padding: const EdgeInsets.symmetric( 51 | vertical: 8.0, 52 | ), 53 | child: Column( 54 | crossAxisAlignment: CrossAxisAlignment.start, 55 | children: [ 56 | Text( 57 | AppLocalizations.of(context).byLabel, 58 | style: LitTextStyles.sansSerif.copyWith( 59 | fontSize: 13.0, 60 | fontWeight: FontWeight.w600, 61 | color: LitColors.mediumGrey.withOpacity( 62 | 0.6, 63 | ), 64 | ), 65 | ), 66 | CleanTextField( 67 | controller: widget.authorController, 68 | focusNode: _authorFocus, 69 | style: LitTextStyles.sansSerif.copyWith( 70 | fontWeight: FontWeight.w700, 71 | ), 72 | maxLines: 1, 73 | ), 74 | ], 75 | ), 76 | ), 77 | ], 78 | ), 79 | ), 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/widgets/secondary_color_selector_card.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A card widget displaying a scrolling list of selectable color cards. 4 | /// 5 | /// The selected color's values are set to be the bookmark's secondary color. 6 | class SecondaryColorSelectorCard extends StatefulWidget { 7 | const SecondaryColorSelectorCard({ 8 | Key? key, 9 | this.buttonBoxShadow = const [ 10 | const BoxShadow( 11 | blurRadius: 2.0, 12 | color: Colors.black12, 13 | offset: Offset(2, 1), 14 | spreadRadius: 0.5, 15 | ), 16 | ], 17 | required this.userCreatedColors, 18 | required this.selectedSecondaryColorValue, 19 | required this.onSelectSecondaryColor, 20 | }) : super(key: key); 21 | 22 | /// The box shadow applied to the buttons. 23 | final List buttonBoxShadow; 24 | 25 | /// The list of selectable colors. 26 | final List userCreatedColors; 27 | 28 | /// The currently selected color (as an integer value). 29 | final int selectedSecondaryColorValue; 30 | 31 | /// The callback to update the selected color. 32 | final void Function(Color color) onSelectSecondaryColor; 33 | 34 | @override 35 | _SecondaryColorSelectorCardState createState() => 36 | _SecondaryColorSelectorCardState(); 37 | } 38 | 39 | class _SecondaryColorSelectorCardState 40 | extends State { 41 | /// States whether the provided [color] is currently selected. 42 | bool _colorIsSelected(Color color) { 43 | return color == Color(widget.selectedSecondaryColorValue); 44 | } 45 | 46 | /// Returns the color values as a [Color] object of an specific item on the 47 | /// provided color list (based on its index). 48 | Color _mapSelectedColor(int index) { 49 | return Color.fromARGB( 50 | widget.userCreatedColors[index].alpha, 51 | widget.userCreatedColors[index].red, 52 | widget.userCreatedColors[index].green, 53 | widget.userCreatedColors[index].blue, 54 | ); 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return Padding( 60 | padding: const EdgeInsets.symmetric( 61 | horizontal: 16.0, 62 | vertical: 8.0, 63 | ), 64 | child: LitTitledActionCard( 65 | title: AppLocalizations.of(context).accentColorLabel.capitalize(), 66 | subtitle: AppLocalizations.of(context).selectAccentColorLabel, 67 | child: Column( 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | children: [ 70 | Padding( 71 | padding: const EdgeInsets.only(top: 8.0), 72 | child: SizedBox( 73 | height: 64.0, 74 | child: LitScrollbar( 75 | child: ListView.builder( 76 | padding: const EdgeInsets.symmetric( 77 | horizontal: 8.0, 78 | ), 79 | physics: BouncingScrollPhysics(), 80 | scrollDirection: Axis.horizontal, 81 | itemCount: widget.userCreatedColors.length, 82 | itemBuilder: (BuildContext context, int index) { 83 | return SelectableColorTile( 84 | onSelectCallback: widget.onSelectSecondaryColor, 85 | color: _mapSelectedColor(index), 86 | selected: _colorIsSelected( 87 | _mapSelectedColor(index), 88 | ), 89 | ); 90 | }, 91 | ), 92 | ), 93 | ), 94 | ), 95 | ], 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/widgets/selectable_color_tile.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class SelectableColorTile extends StatefulWidget { 4 | final Color color; 5 | final List boxShadow; 6 | final double height; 7 | final double width; 8 | final bool selected; 9 | final void Function(Color) onSelectCallback; 10 | 11 | const SelectableColorTile({ 12 | Key? key, 13 | required this.color, 14 | this.boxShadow = const [ 15 | const BoxShadow( 16 | blurRadius: 2.0, 17 | color: Colors.black12, 18 | offset: Offset(2, 1), 19 | spreadRadius: 0.5, 20 | ), 21 | ], 22 | this.height = 64.0, 23 | this.width = 64.0, 24 | required this.selected, 25 | required this.onSelectCallback, 26 | }) : super(key: key); 27 | 28 | @override 29 | _SelectableColorTileState createState() => _SelectableColorTileState(); 30 | } 31 | 32 | class _SelectableColorTileState extends State 33 | with TickerProviderStateMixin { 34 | late AnimationController _animationController; 35 | 36 | void _onSelect() { 37 | _animationController.reverse(from: 1.0).then( 38 | (_) => _animationController.forward().then( 39 | (__) => widget.onSelectCallback(widget.color), 40 | ), 41 | ); 42 | } 43 | 44 | @override 45 | void initState() { 46 | super.initState(); 47 | _animationController = AnimationController( 48 | duration: Duration(milliseconds: 120), 49 | vsync: this, 50 | ); 51 | _animationController.forward(); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return AnimatedBuilder( 57 | animation: _animationController, 58 | builder: (context, _) { 59 | return InkWell( 60 | onTap: _onSelect, 61 | child: SizedBox( 62 | height: widget.height, 63 | width: widget.width, 64 | child: Padding( 65 | padding: const EdgeInsets.all(4.0), 66 | child: Stack( 67 | children: [ 68 | Container( 69 | decoration: BoxDecoration( 70 | color: Color.lerp(Colors.white, widget.color, 71 | (_animationController.value) * 0.85), 72 | borderRadius: BorderRadius.circular(15.0), 73 | boxShadow: widget.boxShadow, 74 | ), 75 | ), 76 | widget.selected 77 | ? Align( 78 | alignment: Alignment.center, 79 | child: Container( 80 | decoration: BoxDecoration( 81 | color: Colors.white, 82 | borderRadius: BorderRadius.all( 83 | Radius.circular( 84 | 6.0, 85 | ), 86 | ), 87 | ), 88 | child: Padding( 89 | padding: const EdgeInsets.symmetric( 90 | horizontal: 6.0, 91 | vertical: 6.0, 92 | ), 93 | child: Icon( 94 | LitIcons.check, 95 | color: LitColors.mediumGrey, 96 | size: 16.0, 97 | ), 98 | ), 99 | ), 100 | ) 101 | : SizedBox(), 102 | ], 103 | )), 104 | ), 105 | ); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/widgets/selected_create_tile.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class SelectedCreateTile extends StatelessWidget { 4 | final String label; 5 | 6 | const SelectedCreateTile({ 7 | Key? key, 8 | required this.label, 9 | }) : super(key: key); 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: const EdgeInsets.symmetric( 14 | vertical: 12.0, 15 | horizontal: 16.0, 16 | ), 17 | child: Container( 18 | decoration: BoxDecoration( 19 | borderRadius: BorderRadius.circular( 20 | 12.0, 21 | ), 22 | color: HexColor('#8e8e8e')), 23 | child: Padding( 24 | padding: const EdgeInsets.symmetric( 25 | vertical: 8.0, 26 | horizontal: 32.0, 27 | ), 28 | child: Text( 29 | "$label", 30 | style: LitTextStyles.sansSerif.copyWith( 31 | fontSize: 16.0, 32 | color: Colors.white, 33 | fontWeight: FontWeight.w700), 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/widgets/striped_design.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A `History of Me` widget implementing a striped [BookmarkDesign] to be 4 | /// used as a bookmark preview based on the [UserData] provided. 5 | class StripedDesign extends StatelessWidget implements BookmarkDesign { 6 | /// The bookmark's radius. 7 | final double radius; 8 | 9 | /// The user data containing the bookmark configuration. 10 | final UserData userData; 11 | 12 | /// Creates a [StripedDesign]. 13 | const StripedDesign({ 14 | Key? key, 15 | required this.radius, 16 | required this.userData, 17 | }) : super(key: key); 18 | 19 | /// The total stripes count. 20 | int get _stripeCount { 21 | return userData.stripeCount; 22 | } 23 | 24 | /// The bookmark height. 25 | double get _height { 26 | return BookmarkCover.height; 27 | } 28 | 29 | /// The bookmark width. 30 | double get _width { 31 | return BookmarkCover.width; 32 | } 33 | 34 | /// Returns the stripe's aspect ratio. 35 | double get _stripeAspectRatio { 36 | return _width / (_height / _stripeCount); 37 | } 38 | 39 | Color get _primaryColor => Color(userData.primaryColor); 40 | 41 | Color get _accentColor => Colors.white; 42 | 43 | /// Returns a list of [_Stripe] widgets. 44 | List get stripes { 45 | List stripeList = []; 46 | 47 | for (int i = 0; i < _stripeCount; i++) { 48 | // Create a two-color pattern by only coloring every second stripe. 49 | if (i % 2 == 0) { 50 | /// Add a colored stripe 51 | stripeList.add( 52 | _Stripe( 53 | aspectRatio: _stripeAspectRatio, 54 | color: _primaryColor, 55 | ), 56 | ); 57 | } else { 58 | /// Add a white stripe 59 | stripeList.add( 60 | _Stripe( 61 | aspectRatio: _stripeAspectRatio, 62 | color: _accentColor, 63 | ), 64 | ); 65 | } 66 | } 67 | 68 | return stripeList; 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return ClipRRect( 74 | borderRadius: BorderRadius.all( 75 | Radius.circular(radius), 76 | ), 77 | child: Stack( 78 | children: [ 79 | // Ensure to paint on a solid background. 80 | Container(color: _accentColor), 81 | Column(children: stripes), 82 | ], 83 | ), 84 | ); 85 | } 86 | } 87 | 88 | /// A stripe on a stripped bookmark design. 89 | class _Stripe extends StatelessWidget { 90 | final double aspectRatio; 91 | final Color color; 92 | const _Stripe({ 93 | Key? key, 94 | required this.aspectRatio, 95 | required this.color, 96 | }) : super(key: key); 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | return AspectRatio( 101 | aspectRatio: aspectRatio, 102 | child: Container( 103 | decoration: BoxDecoration( 104 | color: color, 105 | ), 106 | ), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/widgets/unselected_create_tile.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class UnselectedCreateTile extends StatelessWidget { 4 | final String? label; 5 | 6 | const UnselectedCreateTile({Key? key, this.label}) : super(key: key); 7 | @override 8 | Widget build(BuildContext context) { 9 | return Padding( 10 | padding: const EdgeInsets.symmetric( 11 | vertical: 8.0, 12 | horizontal: 24.0, 13 | ), 14 | child: Text( 15 | "$label", 16 | style: LitTextStyles.sansSerif.copyWith( 17 | fontSize: 16.0, 18 | color: LitColors.darkGrey, 19 | fontWeight: FontWeight.w700, 20 | ), 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/widgets/unsupported_file_dialog.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A dialog widget showing a information message that the desired file is not 4 | /// supported by this app. 5 | class UnsupportedFileDialog extends StatefulWidget { 6 | /// Creates a [UnsupportedFileDialog]. 7 | const UnsupportedFileDialog({Key? key}) : super(key: key); 8 | 9 | @override 10 | State createState() => _UnsupportedFileDialogState(); 11 | } 12 | 13 | class _UnsupportedFileDialogState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return LitTitledDialog( 17 | titleText: LeitmotifLocalizations.of(context).unsupportedFileTitle, 18 | margin: LitEdgeInsets.none, 19 | child: CleanInkWell( 20 | onTap: () => Navigator.of(context).pop(), 21 | child: Padding( 22 | padding: LitEdgeInsets.dialogMargin, 23 | child: LitDescriptionTextBox( 24 | text: LeitmotifLocalizations.of(context).unsupportedFileDescr, 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/widgets/updated_label_text.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class UpdatedLabelText extends StatelessWidget { 4 | final TextAlign textAlign; 5 | final TextStyle textStyle; 6 | 7 | final int? lastUpdateTimestamp; 8 | const UpdatedLabelText({ 9 | Key? key, 10 | this.textAlign = TextAlign.left, 11 | required this.lastUpdateTimestamp, 12 | this.textStyle = LitSansSerifStyles.caption, 13 | }) : super(key: key); 14 | @override 15 | Widget build(BuildContext context) { 16 | final RelativeDateTime relativeDateTime = RelativeDateTime( 17 | dateTime: DateTime.now(), 18 | other: DateTime.fromMillisecondsSinceEpoch(lastUpdateTimestamp!), 19 | ); 20 | final RelativeDateFormat relativeDateFormatter = RelativeDateFormat( 21 | Localizations.localeOf(context), 22 | ); 23 | //final String formatter = DateFormat().toString(); 24 | return ClippedText( 25 | "${relativeDateFormatter.format(relativeDateTime)}", 26 | textAlign: textAlign, 27 | style: textStyle, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/widgets/user_icon.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | class UserIcon extends StatelessWidget { 4 | final UserData userData; 5 | final double size; 6 | final void Function()? onPressed; 7 | const UserIcon({ 8 | Key? key, 9 | required this.size, 10 | required this.userData, 11 | this.onPressed, 12 | }) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return LitUserIcon( 17 | size: size, 18 | name: userData.name, 19 | color: Color(userData.primaryColor), 20 | accentColor: Color(userData.secondaryColor), 21 | onPressed: onPressed, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/widgets/user_profile_card.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A widget displaying the user's analytics and basic customization options. 4 | /// 5 | /// This includes an user icon, a button allowing to change the username and a 6 | /// card listing the user's statistics. 7 | class UserProfileCard extends StatefulWidget { 8 | /// The provided [UserData]. 9 | final UserData userData; 10 | 11 | /// A callback to handle the 'on pressed' action. 12 | final void Function() onPressedUserIcon; 13 | 14 | /// Creates a [UserProfileCard]. 15 | const UserProfileCard({ 16 | Key? key, 17 | required this.userData, 18 | required this.onPressedUserIcon, 19 | }) : super(key: key); 20 | @override 21 | _UserProfileCardState createState() => _UserProfileCardState(); 22 | } 23 | 24 | class _UserProfileCardState extends State { 25 | /// Returns a localized date string based the provided `created` timestamp. 26 | String get localizedCreatedLabel => DateTime.fromMillisecondsSinceEpoch( 27 | widget.userData.created, 28 | ).formatAsLocalizedDate(context); 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return LitConstrainedSizedBox( 33 | child: Padding( 34 | padding: const EdgeInsets.symmetric( 35 | horizontal: 16.0, 36 | vertical: 8.0, 37 | ), 38 | child: Column( 39 | mainAxisAlignment: MainAxisAlignment.center, 40 | crossAxisAlignment: CrossAxisAlignment.center, 41 | children: [ 42 | UserIcon( 43 | size: 96.0, 44 | userData: widget.userData, 45 | onPressed: widget.onPressedUserIcon, 46 | ), 47 | Padding( 48 | padding: LitEdgeInsets.card, 49 | child: ClippedText( 50 | widget.userData.name, 51 | style: LitSansSerifStyles.h6, 52 | ), 53 | ), 54 | LayoutBuilder( 55 | builder: (context, constraints) { 56 | return SizedBox( 57 | width: constraints.maxWidth, 58 | child: Row( 59 | mainAxisAlignment: MainAxisAlignment.center, 60 | children: [ 61 | SizedBox( 62 | width: constraints.maxWidth / 2, 63 | child: Column( 64 | mainAxisAlignment: MainAxisAlignment.center, 65 | crossAxisAlignment: CrossAxisAlignment.center, 66 | children: [ 67 | Padding( 68 | padding: 69 | const EdgeInsets.symmetric(vertical: 4.0), 70 | child: ClippedText( 71 | LeitmotifLocalizations.of(context) 72 | .createdOnLabel, 73 | style: LitSansSerifStyles.caption, 74 | ), 75 | ), 76 | Padding( 77 | padding: 78 | const EdgeInsets.symmetric(vertical: 4.0), 79 | child: ClippedText( 80 | localizedCreatedLabel, 81 | style: LitSansSerifStyles.subtitle2, 82 | ), 83 | ), 84 | ], 85 | ), 86 | ), 87 | ], 88 | ), 89 | ); 90 | }, 91 | ), 92 | ], 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/widgets/word_count_badge.dart: -------------------------------------------------------------------------------- 1 | part of widgets; 2 | 3 | /// A [LitBadge] widget displaying the total word count provided by the text 4 | /// editing controller's current input value. 5 | /// 6 | /// Applies validation to ensure only valid words are counted. 7 | class WordCountBadge extends StatelessWidget { 8 | final TextEditingController controller; 9 | 10 | /// Creates a [WordCountBadge]. 11 | const WordCountBadge({ 12 | Key? key, 13 | required this.controller, 14 | }) : super(key: key); 15 | 16 | /// Returns the total word count. 17 | int get _totalWords => controller.text.wordCount; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return LitBadge( 22 | backgroundColor: LitColors.grey300, 23 | child: Text( 24 | _totalWords.toString() + 25 | " " + 26 | AppLocalizations.of(context).wordsWrittenLabel, 27 | style: LitSansSerifStyles.caption.copyWith( 28 | color: Colors.white, 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: history_of_me 2 | description: Your own personal diary. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.6.0+10 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | flutter_localizations: 27 | sdk: flutter 28 | 29 | intl: ^0.17.0 30 | hive_flutter: ^1.1.0 31 | hive_generator: ^1.1.1 32 | build_runner: ^2.1.7 33 | path_provider: ^2.0.3 34 | image_picker: ^0.8.4+10 35 | package_info_plus: ^1.0.6 36 | leitmotif: 37 | ^2.2.0 38 | # Specify alternative leitmotif-flutter locations 39 | # git: 40 | # url: https://github.com/litlifesoftware/leitmotif-flutter.git 41 | # path: ../leitmotif-flutter/ 42 | date_colors: 43 | ^0.0.1 44 | # Specify alternative date_colors locations 45 | # git: 46 | # url: https://github.com/litlifesoftware/date_colors.git 47 | # path: ../date_colors/ 48 | lit_relative_date_time: ^1.2.0 49 | lit_backup_service: 50 | ^1.0.3 51 | # Specify alternative leitmotif-flutter locations 52 | # git: 53 | # url: https://github.com/litlifesoftware/lit_backup_service.git 54 | # path: ../lit_backup_service/ 55 | 56 | dev_dependencies: 57 | # For information on the generic Dart part of this file, see the 58 | # following page: https://dart.dev/tools/pub/pubspec 59 | 60 | flutter_launcher_icons: ^0.10.0 61 | 62 | # The following section is specific to Flutter. 63 | flutter: 64 | # The following line ensures that the Material Icons font is 65 | # included with your application, so that you can use the icons in 66 | # the material Icons class. 67 | uses-material-design: true 68 | 69 | # To add assets to your application, add an assets section, like this: 70 | # assets: 71 | # - images/a_dot_burr.jpeg 72 | # - images/a_dot_ham.jpeg 73 | assets: 74 | - assets/icon/ 75 | - assets/images/ 76 | 77 | # An image asset can refer to one or more resolution-specific "variants", see 78 | # https://flutter.dev/assets-and-images/#resolution-aware. 79 | 80 | # For details regarding adding assets from package dependencies, see 81 | # https://flutter.dev/assets-and-images/#from-packages 82 | 83 | # To add custom fonts to your application, add a fonts section here, 84 | # in this "flutter" section. Each entry in this list should have a 85 | # "family" key with the font family name, and a "fonts" key with a 86 | # list giving the asset and other descriptors for the font. For 87 | # example: 88 | fonts: 89 | - family: Playfair Display 90 | fonts: 91 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-Black.ttf 92 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-BlackItalic.ttf 93 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-Bold.ttf 94 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-BoldItalic.ttf 95 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-Italic.ttf 96 | - asset: assets/fonts/PlayfairDisplay/PlayfairDisplay-Regular.ttf 97 | # fonts: 98 | # - family: Schyler 99 | # fonts: 100 | # - asset: fonts/Schyler-Regular.ttf 101 | # - asset: fonts/Schyler-Italic.ttf 102 | # style: italic 103 | # - family: Trajan Pro 104 | # fonts: 105 | # - asset: fonts/TrajanPro.ttf 106 | # - asset: fonts/TrajanPro_Bold.ttf 107 | # weight: 700 108 | # 109 | # For details regarding fonts from package dependencies, 110 | # see https://flutter.dev/custom-fonts/#from-packages 111 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/litlifesoftware/HistoryOfMe/aa7eae85732059bbab743dc6145cd09b03dfe17e/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | history_of_me 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "history_of_me", 3 | "short_name": "history_of_me", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------