├── noterly
├── .vscode
│ ├── settings.json
│ ├── launch.json
│ └── tasks.json
├── assets
│ ├── icon
│ │ ├── app_icon_512.png
│ │ ├── app_icon_bg_512.png
│ │ └── app_icon_fg_512.png
│ ├── google_fonts
│ │ ├── DMSans-Bold.ttf
│ │ ├── DMSans-Regular.ttf
│ │ └── OFL.txt
│ └── i18n
│ │ ├── en_GB.json
│ │ └── en_US.json
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_background.png
│ │ │ │ │ │ ├── ic_launcher_foreground.png
│ │ │ │ │ │ └── ic_launcher_monochrome.png
│ │ │ │ │ ├── drawable-hdpi
│ │ │ │ │ │ └── ic_shortcut_add.png
│ │ │ │ │ ├── drawable-mdpi
│ │ │ │ │ │ └── ic_shortcut_add.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ ├── notification_icon_48.png
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-xhdpi
│ │ │ │ │ │ └── ic_shortcut_add.png
│ │ │ │ │ ├── drawable-xxhdpi
│ │ │ │ │ │ └── ic_shortcut_add.png
│ │ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ │ │ └── ic_shortcut_add.png
│ │ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ │ └── ic_launcher.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── uk
│ │ │ │ │ │ └── co
│ │ │ │ │ │ └── tdsstudios
│ │ │ │ │ │ └── noterly
│ │ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ │ └── MyTileService.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ ├── google-services.json
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── lib
│ ├── managers
│ │ ├── log.dart
│ │ ├── lifecycle_event_handler.dart
│ │ ├── isolate_manager.dart
│ │ ├── file_manager.dart
│ │ └── app_manager.dart
│ ├── extensions
│ │ ├── time_of_day_extensions.dart
│ │ ├── duration_extensions.dart
│ │ └── date_time_extensions.dart
│ ├── models
│ │ ├── navigation_screen.dart
│ │ ├── duration_data.dart
│ │ ├── repetition_data.dart
│ │ ├── app_data.dart
│ │ └── notification_item.dart
│ ├── build_info.dart
│ ├── widgets
│ │ ├── item_list_decoration.dart
│ │ ├── notification_list.dart
│ │ ├── colour_picker.dart
│ │ ├── duration_picker.dart
│ │ └── repetition_picker.dart
│ ├── firebase_options.dart
│ └── pages
│ │ └── main_page
│ │ └── archived_notifications_page.dart
├── .gitignore
├── .metadata
├── analysis_options.yaml
└── pubspec.yaml
├── website
├── scss
│ ├── layout
│ │ ├── _navigation.scss
│ │ └── _layout.scss
│ ├── core
│ │ ├── _misc_base.scss
│ │ ├── _typography.scss
│ │ └── _reset.scss
│ ├── sections
│ │ ├── _about.scss
│ │ ├── _footer.scss
│ │ └── _hero.scss
│ ├── app.scss
│ ├── abstracts
│ │ ├── _mixins.scss
│ │ └── _variables.scss
│ └── components
│ │ └── _card.scss
├── robots.txt
├── img
│ ├── icon
│ │ ├── favicon.ico
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── apple-touch-icon.png
│ │ ├── android-chrome-192x192.png
│ │ └── android-chrome-512x512.png
│ ├── play_store_feature_graphic.png
│ ├── screenshots
│ │ └── noterly_app_main_page.png
│ ├── logo
│ │ ├── logo_background.svg
│ │ └── logo_foreground.svg
│ └── google_play_badge.svg
├── .vscode
│ ├── settings.json
│ └── tasks.json
└── css
│ └── app.css
├── scripts
├── i18n
│ ├── .gitignore
│ ├── i18n.py
│ └── out
│ │ ├── en_US.json
│ │ └── en_GB.json
└── .gitignore
├── .gitattributes
├── assets
├── figma
│ ├── logo_full_2048.png
│ ├── screenshot_1.png
│ ├── screenshot_2.png
│ ├── screenshot_3.png
│ ├── screenshot_4.png
│ ├── screenshot_5.png
│ ├── feedback_form_header.png
│ ├── logo_background_2048.png
│ ├── logo_foreground_2048.png
│ ├── logo_full_circle_2048.png
│ ├── notification_icon_48.png
│ ├── logo_full_rounded_2048.png
│ ├── play_store_feature_graphic.png
│ ├── logo_foreground_themed_2048.png
│ ├── instagram
│ │ └── instagam_announcement.png
│ ├── quick_action_new_note_icon_192.png
│ ├── logo_background.svg
│ ├── logo_foreground_themed.svg
│ ├── notification_icon.svg
│ ├── logo_foreground.svg
│ ├── logo_full.svg
│ ├── logo_full_circle.svg
│ └── logo_full_rounded.svg
├── illustrator
│ ├── Noterly Icons.ai
│ ├── 24w
│ │ ├── app_icon_24.png
│ │ ├── app_icon_bg_24.png
│ │ ├── app_icon_fg_24.png
│ │ └── notification_icon_24.png
│ ├── 48w
│ │ ├── app_icon_48.png
│ │ ├── app_icon_bg_48.png
│ │ ├── app_icon_fg_48.png
│ │ └── notification_icon_48.png
│ ├── 1024w
│ │ ├── app_icon_1024.png
│ │ ├── app_icon_bg_1024.png
│ │ ├── app_icon_fg_1024.png
│ │ └── notification_icon_1024.png
│ └── 512w
│ │ └── notification_icon_512.png
├── icon_kitchen
│ ├── merged
│ │ ├── play_store_512.png
│ │ └── android
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ └── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ ├── regular
│ │ └── android
│ │ │ ├── play_store_512.png
│ │ │ └── res
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ └── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ └── themed
│ │ └── android
│ │ └── res
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_monochrome.png
│ │ └── mipmap-anydpi-v26
│ │ └── ic_launcher.xml
└── screenshots
│ ├── Screenshot_1675286787.png
│ ├── Screenshot_1675286856.png
│ ├── Screenshot_1675286858.png
│ ├── Screenshot_1675286862.png
│ ├── Screenshot_1675286872.png
│ ├── Screenshot_1675286878.png
│ └── Screenshot_1675286893.png
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── FUNDING.yml
└── README.md
/noterly/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/website/scss/layout/_navigation.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/scripts/i18n/.gitignore:
--------------------------------------------------------------------------------
1 | credentials.json
--------------------------------------------------------------------------------
/website/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: /
--------------------------------------------------------------------------------
/scripts/.gitignore:
--------------------------------------------------------------------------------
1 | adb-and-related-commands.txt
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/website/img/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/favicon.ico
--------------------------------------------------------------------------------
/website/scss/core/_misc_base.scss:
--------------------------------------------------------------------------------
1 | ::selection {
2 | color: $on-primary;
3 | background: $primary;
4 | }
--------------------------------------------------------------------------------
/assets/figma/logo_full_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_full_2048.png
--------------------------------------------------------------------------------
/assets/figma/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/screenshot_1.png
--------------------------------------------------------------------------------
/assets/figma/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/screenshot_2.png
--------------------------------------------------------------------------------
/assets/figma/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/screenshot_3.png
--------------------------------------------------------------------------------
/assets/figma/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/screenshot_4.png
--------------------------------------------------------------------------------
/assets/figma/screenshot_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/screenshot_5.png
--------------------------------------------------------------------------------
/assets/illustrator/Noterly Icons.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/Noterly Icons.ai
--------------------------------------------------------------------------------
/noterly/assets/icon/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/assets/icon/app_icon_512.png
--------------------------------------------------------------------------------
/website/img/icon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/favicon-16x16.png
--------------------------------------------------------------------------------
/website/img/icon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/favicon-32x32.png
--------------------------------------------------------------------------------
/assets/figma/feedback_form_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/feedback_form_header.png
--------------------------------------------------------------------------------
/assets/figma/logo_background_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_background_2048.png
--------------------------------------------------------------------------------
/assets/figma/logo_foreground_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_foreground_2048.png
--------------------------------------------------------------------------------
/assets/figma/logo_full_circle_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_full_circle_2048.png
--------------------------------------------------------------------------------
/assets/figma/notification_icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/notification_icon_48.png
--------------------------------------------------------------------------------
/assets/illustrator/24w/app_icon_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/24w/app_icon_24.png
--------------------------------------------------------------------------------
/assets/illustrator/48w/app_icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/48w/app_icon_48.png
--------------------------------------------------------------------------------
/noterly/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/website/img/icon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/apple-touch-icon.png
--------------------------------------------------------------------------------
/assets/figma/logo_full_rounded_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_full_rounded_2048.png
--------------------------------------------------------------------------------
/assets/illustrator/24w/app_icon_bg_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/24w/app_icon_bg_24.png
--------------------------------------------------------------------------------
/assets/illustrator/24w/app_icon_fg_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/24w/app_icon_fg_24.png
--------------------------------------------------------------------------------
/assets/illustrator/48w/app_icon_bg_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/48w/app_icon_bg_48.png
--------------------------------------------------------------------------------
/assets/illustrator/48w/app_icon_fg_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/48w/app_icon_fg_48.png
--------------------------------------------------------------------------------
/noterly/assets/icon/app_icon_bg_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/assets/icon/app_icon_bg_512.png
--------------------------------------------------------------------------------
/noterly/assets/icon/app_icon_fg_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/assets/icon/app_icon_fg_512.png
--------------------------------------------------------------------------------
/assets/figma/play_store_feature_graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/play_store_feature_graphic.png
--------------------------------------------------------------------------------
/assets/illustrator/1024w/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/1024w/app_icon_1024.png
--------------------------------------------------------------------------------
/noterly/assets/google_fonts/DMSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/assets/google_fonts/DMSans-Bold.ttf
--------------------------------------------------------------------------------
/website/img/icon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/website/img/icon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/icon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/website/img/play_store_feature_graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/play_store_feature_graphic.png
--------------------------------------------------------------------------------
/website/scss/core/_typography.scss:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: 'DM Sans', sans-serif;
3 | }
4 |
5 | p {
6 | line-height: 1.75rem;
7 | }
--------------------------------------------------------------------------------
/assets/figma/logo_foreground_themed_2048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/logo_foreground_themed_2048.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/play_store_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/play_store_512.png
--------------------------------------------------------------------------------
/assets/illustrator/1024w/app_icon_bg_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/1024w/app_icon_bg_1024.png
--------------------------------------------------------------------------------
/assets/illustrator/1024w/app_icon_fg_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/1024w/app_icon_fg_1024.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286787.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286787.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286856.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286856.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286858.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286858.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286862.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286862.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286872.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286872.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286878.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286878.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_1675286893.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/screenshots/Screenshot_1675286893.png
--------------------------------------------------------------------------------
/noterly/assets/google_fonts/DMSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/assets/google_fonts/DMSans-Regular.ttf
--------------------------------------------------------------------------------
/assets/figma/instagram/instagam_announcement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/instagram/instagam_announcement.png
--------------------------------------------------------------------------------
/assets/figma/quick_action_new_note_icon_192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/figma/quick_action_new_note_icon_192.png
--------------------------------------------------------------------------------
/assets/illustrator/24w/notification_icon_24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/24w/notification_icon_24.png
--------------------------------------------------------------------------------
/assets/illustrator/48w/notification_icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/48w/notification_icon_48.png
--------------------------------------------------------------------------------
/assets/illustrator/1024w/notification_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/1024w/notification_icon_1024.png
--------------------------------------------------------------------------------
/assets/illustrator/512w/notification_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/illustrator/512w/notification_icon_512.png
--------------------------------------------------------------------------------
/website/img/screenshots/noterly_app_main_page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/website/img/screenshots/noterly_app_main_page.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/play_store_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/play_store_512.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable-hdpi/ic_shortcut_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable-hdpi/ic_shortcut_add.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable-mdpi/ic_shortcut_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable-mdpi/ic_shortcut_add.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable/notification_icon_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable/notification_icon_48.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable-xhdpi/ic_shortcut_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable-xhdpi/ic_shortcut_add.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable-xxhdpi/ic_shortcut_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable-xxhdpi/ic_shortcut_add.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable-xxxhdpi/ic_shortcut_add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/drawable-xxxhdpi/ic_shortcut_add.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/noterly/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/merged/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/regular/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tomc128/noterly/HEAD/assets/icon_kitchen/themed/android/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/noterly/lib/managers/log.dart:
--------------------------------------------------------------------------------
1 | import 'package:logger/logger.dart';
2 | import 'package:noterly/managers/app_manager.dart';
3 |
4 | class Log {
5 | static Logger get logger => AppManager.instance.logger;
6 | }
7 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/kotlin/uk/co/tdsstudios/noterly/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package uk.co.tdsstudios.noterly
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/noterly/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
6 |
--------------------------------------------------------------------------------
/website/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveSassCompile.settings.formats": [
3 | {
4 | "format": "compressed",
5 | "extensionName": ".css",
6 | "savePath": "/css",
7 | "savePathReplacementPairs": null
8 | }
9 | ]
10 | }
--------------------------------------------------------------------------------
/noterly/lib/extensions/time_of_day_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | extension TimeOfDayExtensions on TimeOfDay {
4 | bool isAfterNow() {
5 | var now = TimeOfDay.now();
6 | return hour > now.hour || (hour == now.hour && minute > now.minute);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/noterly/lib/models/navigation_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NavigationScreen extends StatelessWidget {
4 | const NavigationScreen({
5 | super.key,
6 | });
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return const Placeholder();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/noterly/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/noterly/lib/build_info.dart:
--------------------------------------------------------------------------------
1 | class BuildInfo {
2 | static const String appVersion = 'UNSET_VERSION';
3 | static const int buildNumber = -80085;
4 | static const ReleaseType releaseType = ReleaseType.inDev;
5 | static const String branch = 'UNSET_BRANCH';
6 | }
7 |
8 | enum ReleaseType {
9 | stable,
10 | beta,
11 | personalTest,
12 | inDev,
13 | }
14 |
--------------------------------------------------------------------------------
/website/scss/sections/_about.scss:
--------------------------------------------------------------------------------
1 | section#about {
2 | background: linear-gradient(135deg, #BB00FD, #5B00B6);
3 | margin-top: -$wave-height;
4 | z-index: 10;
5 | padding-top: calc($wave-height + 15rem);
6 | padding-bottom: calc($wave-height + 15rem);
7 | width: 100%;
8 |
9 | .content {
10 | .card-grid {
11 | width: 100%;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/icon_kitchen/merged/android/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/icon_kitchen/regular/android/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/assets/icon_kitchen/themed/android/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/website/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Upload files to web server",
8 | "type": "shell",
9 | "command": "scp -P 222 -r ${workspaceFolder}/* tomc128@5.198.125.182:/opt/docker/caddy/sites/noterly/"
10 | }
11 | ]
12 | }
--------------------------------------------------------------------------------
/assets/figma/logo_background.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/website/img/logo/logo_background.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/website/scss/app.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap');
2 |
3 | @import 'abstracts/mixins';
4 | @import 'abstracts/variables';
5 |
6 | @import 'core/reset';
7 | @import 'core/misc_base';
8 | @import 'core/typography';
9 |
10 | @import 'components/card';
11 |
12 | @import 'layout/layout';
13 | @import 'layout/navigation';
14 |
15 | @import 'sections/hero';
16 | @import 'sections/about';
17 | @import 'sections/footer';
--------------------------------------------------------------------------------
/noterly/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/noterly/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/assets/figma/logo_foreground_themed.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/noterly/lib/widgets/item_list_decoration.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ItemListDecoration extends StatelessWidget {
4 | final Color colour;
5 |
6 | const ItemListDecoration({
7 | required this.colour,
8 | super.key,
9 | });
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return SizedBox(
14 | width: 32,
15 | child: Align(
16 | alignment: Alignment.center,
17 | child: CircleAvatar(
18 | radius: 8,
19 | backgroundColor: colour,
20 | ),
21 | ),
22 | );
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/assets/figma/notification_icon.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/noterly/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "noterly",
9 | "request": "launch",
10 | "type": "dart"
11 | },
12 | {
13 | "name": "noterly (profile mode)",
14 | "request": "launch",
15 | "type": "dart",
16 | "flutterMode": "profile"
17 | },
18 | {
19 | "name": "noterly (release mode)",
20 | "request": "launch",
21 | "type": "dart",
22 | "flutterMode": "release"
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help improve Noterly
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Version (please complete the following information):**
27 | - Device:
28 | - Android version:
29 | - Noterly version
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: tomchapman128
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/website/scss/layout/_layout.scss:
--------------------------------------------------------------------------------
1 | section>.content,
2 | footer>.content {
3 | max-width: 1200px;
4 | width: 100%;
5 |
6 | @include display-small {
7 | padding: 0 $small-side-padding;
8 | }
9 |
10 | padding: 0 $side-padding;
11 |
12 | }
13 |
14 | section,
15 | footer {
16 | position: relative;
17 | display: flex;
18 | align-items: center;
19 | flex-direction: column;
20 | }
21 |
22 | .adaptive-grid {
23 | display: grid;
24 |
25 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
26 | @include display-xsmall {
27 | // On extra small screens, the grid is 100% width so it does not overflow
28 | grid-template-columns: repeat(auto-fit, minmax(100%, 1fr));
29 | }
30 |
31 | gap: 2rem;
32 | @include display-small {
33 | gap: 1rem;
34 | }
35 | margin: 2rem 0;
36 | }
--------------------------------------------------------------------------------
/website/scss/abstracts/_mixins.scss:
--------------------------------------------------------------------------------
1 |
2 | // Breakpoints
3 | $bp-xxsmall: 12em;
4 | $bp-xsmall: 24em;
5 | $bp-small: 48em; // 768px
6 | $bp-medium: 64em; // 1024px
7 | $bp-large: 85.375em; // 1366px
8 | $bp-xlarge: 120em; // 1920px
9 | $bp-xxlarge: 160em; // 2560px
10 |
11 | // Media Queries
12 | $mq-xxsmall: "(max-width: #{$bp-xxsmall})";
13 | $mq-xsmall: "(max-width: #{$bp-xsmall})";
14 | $mq-small: "(max-width: #{$bp-small})";
15 | $mq-medium: "(max-width: #{$bp-medium})";
16 |
17 | @mixin display-xxsmall {
18 | @media #{$mq-xxsmall} {
19 | @content;
20 | }
21 | }
22 | @mixin display-xsmall {
23 | @media #{$mq-xsmall} {
24 | @content;
25 | }
26 | }
27 | @mixin display-small {
28 | @media #{$mq-small} {
29 | @content;
30 | }
31 | }
32 | @mixin display-medium {
33 | @media #{$mq-medium} {
34 | @content;
35 | }
36 | }
--------------------------------------------------------------------------------
/noterly/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/noterly/lib/widgets/notification_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:noterly/models/notification_item.dart';
3 |
4 | class NotificationList extends StatelessWidget {
5 | final List items;
6 | final Function onRefresh;
7 | final String emptyText;
8 | final Widget? Function(BuildContext, int) itemBuilder;
9 |
10 | const NotificationList({
11 | required this.items,
12 | required this.onRefresh,
13 | required this.emptyText,
14 | required this.itemBuilder,
15 | super.key,
16 | });
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | Widget emptyListView() {
21 | return Center(
22 | child: Text(emptyText),
23 | );
24 | }
25 |
26 | return items.isEmpty
27 | ? emptyListView()
28 | : ListView.builder(
29 | itemCount: items.length,
30 | itemBuilder: itemBuilder,
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/noterly/lib/managers/lifecycle_event_handler.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class LifecycleEventHandler extends WidgetsBindingObserver {
5 | final AsyncCallback? resumeCallback;
6 | final AsyncCallback? suspendingCallback;
7 |
8 | LifecycleEventHandler({
9 | this.resumeCallback,
10 | this.suspendingCallback,
11 | });
12 |
13 | @override
14 | Future didChangeAppLifecycleState(AppLifecycleState state) async {
15 | switch (state) {
16 | case AppLifecycleState.resumed:
17 | if (resumeCallback != null) {
18 | await resumeCallback!();
19 | }
20 | break;
21 | case AppLifecycleState.inactive:
22 | case AppLifecycleState.paused:
23 | case AppLifecycleState.detached:
24 | case AppLifecycleState.hidden:
25 | if (suspendingCallback != null) {
26 | await suspendingCallback!();
27 | }
28 | break;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/noterly/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 |
13 | // INFO: Workaround for 'libflutter.so' crash. This makes non-ARM devices emulate ARM, instead of trying
14 | // to run directly and fail. Unsure if this will work but it's worth a try.
15 | gradle.beforeProject({ project ->
16 | if (project.hasProperty("target-platform") &&
17 | !project.getProperty("target-platform").split(",").contains("android-arm")) {
18 | project.setProperty("target-platform", "android-arm,android-arm64")
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/website/scss/components/_card.scss:
--------------------------------------------------------------------------------
1 | // generate a card component, fitting in with the rest of the site
2 | // should have the dark gradient background
3 |
4 | .card {
5 | background: $dark-gradient;
6 | padding: $card-padding;
7 | @include display-small {
8 | padding: $small-card-padding;
9 | }
10 |
11 | min-height: 20rem;
12 | border-radius: $border-radius;
13 | z-index: 10;
14 |
15 | box-shadow: $large-shadow;
16 |
17 | .content {
18 | display: flex;
19 | flex-direction: column;
20 | align-items: start;
21 | justify-content: center;
22 | gap: 1rem;
23 |
24 | iconify-icon {
25 | color: $text-primary-title;
26 | font-size: 3rem;
27 | }
28 |
29 | h2 {
30 | color: $text-primary-title;
31 | text-shadow: $large-shadow;
32 | font-size: 2rem;
33 | }
34 |
35 | p {
36 | color: $text-primary;
37 | font-size: 1.2rem;
38 | }
39 | }
40 |
41 |
42 | }
--------------------------------------------------------------------------------
/noterly/.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.
5 |
6 | version:
7 | revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea
8 | channel: beta
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea
17 | base_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea
18 | - platform: android
19 | create_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea
20 | base_revision: 099b3f4bf1581796fac3848d3381dbf2f29edbea
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/website/scss/abstracts/_variables.scss:
--------------------------------------------------------------------------------
1 | $text-primary-title: #e4b5ff;
2 | $text-primary: #e2b3ff;
3 |
4 | $primary: #aa00f1;
5 | $on-primary: #ffffff;
6 |
7 | $divider: rgba(226, 179, 255, 0.25);
8 |
9 | $dark-one: #1A131F;
10 | $dark-two: #0F041B;
11 | $dark-gradient: linear-gradient(135deg, $dark-one, $dark-two);
12 |
13 | $large-shadow: 0px 5.1px 4.3px rgba(0, 0, 0, 0.042), 0px 14.1px 11.8px rgba(0, 0, 0, 0.06),
14 | 0px 34.1px 28.3px rgba(0, 0, 0, 0.078), 0px 113px 94px rgba(0, 0, 0, 0.12);
15 |
16 | $large-shadow-drop-shadow: drop-shadow(0px 5.1px 4.3px rgba(0, 0, 0, 0.042)) drop-shadow(0px 14.1px 11.8px rgba(0, 0, 0, 0.06)) drop-shadow(0px 34.1px 28.3px rgba(0, 0, 0, 0.078)) drop-shadow(0px 113px 94px rgba(0, 0, 0, 0.12));
17 |
18 | $wave-height: 20rem;
19 | $footer-height: 35vh;
20 |
21 | $logo-size: clamp(4rem, 20vw, 8rem);
22 | $logo-border-radius: clamp(1rem, 5vw, 2rem);
23 |
24 | $hero-title-size: clamp(2.5rem, 10vw, 4rem);
25 | $hero-subtitle-size: clamp(1.5rem, 5vw, 1.75rem);
26 |
27 | $border-radius: 1rem;
28 |
29 | $small-side-padding: 1rem;
30 | $side-padding: 2rem;
31 |
32 | $card-padding: 4rem 2rem;
33 | $small-card-padding: 2rem 1rem;
--------------------------------------------------------------------------------
/noterly/lib/extensions/duration_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_translate/flutter_translate.dart';
2 |
3 | extension DurationExtensions on Duration {
4 | String toRelativeDurationString() {
5 | final days = inDays;
6 | final hours = inHours % 24;
7 | final minutes = inMinutes % 60;
8 |
9 | final daysString = days == 0
10 | ? ''
11 | : days == 1
12 | ? '1 ${translate('time.day')}'
13 | : translate('time.days.value', args: {'value': days});
14 | final hoursString = hours == 0
15 | ? ''
16 | : hours == 1
17 | ? '1 ${translate('time.hour')}'
18 | : translate('time.hours.value', args: {'value': hours});
19 | final minutesString = minutes == 0
20 | ? ''
21 | : minutes == 1
22 | ? '1 ${translate('time.minute')}'
23 | : translate('time.minutes.value', args: {'value': minutes});
24 |
25 | final combined = '$daysString, $hoursString, $minutesString'.trim();
26 | return combined.replaceAll(RegExp(r', ,'), ',').replaceAll(RegExp(r',$'), '').replaceAll(RegExp(r'^,\s*'), ''); // Remove trailing, leading or double commas
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/kotlin/uk/co/tdsstudios/noterly/MyTileService.kt:
--------------------------------------------------------------------------------
1 | package uk.co.tdsstudios.noterly
2 |
3 | import android.annotation.TargetApi
4 | import android.content.Intent
5 | import android.os.Build
6 | import android.service.quicksettings.TileService
7 | import androidx.annotation.RequiresApi
8 | import io.flutter.Log
9 |
10 | @TargetApi(Build.VERSION_CODES.N)
11 | @RequiresApi(Build.VERSION_CODES.N)
12 | class MyTileService : TileService() {
13 | override fun onClick() {
14 | super.onClick()
15 |
16 | try {
17 | // val newIntent = FlutterActivity.withNewEngine().dartEntrypointArgs(listOf("launchFromQuickTile")).build(this)
18 |
19 | // val newIntent = Intent(this, MainActivity::class.java)
20 |
21 | val newIntent = Intent(
22 | "uk.co.tdsstudios.noterly.ACTION_CREATE_NOTE",
23 | null,
24 | this,
25 | MainActivity::class.java
26 | )
27 |
28 |
29 | newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
30 | startActivityAndCollapse(newIntent)
31 | } catch (e: Exception) {
32 | Log.d("debug", "Exception ${e.toString()}")
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/noterly/lib/managers/isolate_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:isolate';
2 | import 'dart:ui';
3 |
4 | import 'package:noterly/managers/log.dart';
5 |
6 | import 'app_manager.dart';
7 |
8 | class IsolateManager {
9 | static final mainRecievePort = ReceivePort();
10 | static const String mainPortName = 'main_isolate_port';
11 |
12 | static void init() {
13 | // Register the port with the main isolate
14 | var registerResult = IsolateNameServer.registerPortWithName(mainRecievePort.sendPort, mainPortName);
15 | if (!registerResult) {
16 | IsolateNameServer.removePortNameMapping(mainPortName);
17 | registerResult = IsolateNameServer.registerPortWithName(mainRecievePort.sendPort, mainPortName);
18 |
19 | if (!registerResult) {
20 | throw IsolateSpawnException('Failed to register port with main isolate (x2)');
21 | }
22 | }
23 |
24 | // Listen for messages from the background isolate
25 | mainRecievePort.listen((message) {
26 | switch (message) {
27 | case 'update':
28 | Log.logger.d('Forcing a full update...');
29 | AppManager.instance.fullUpdate();
30 | break;
31 | default:
32 | Log.logger.w('Unknown message from background isolate: "$message"');
33 | }
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/noterly/lib/models/duration_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_translate/flutter_translate.dart';
2 |
3 | enum DurationType {
4 | hourly('time.hour', 'time.hours'),
5 | daily('time.day', 'time.days'),
6 | weekly('time.week', 'time.weeks'),
7 | monthly('time.month', 'time.months'),
8 | yearly('time.year', 'time.years');
9 |
10 | final String translationKey;
11 | final String sTranslationKey;
12 |
13 | const DurationType(this.translationKey, this.sTranslationKey);
14 | }
15 |
16 | class DurationData {
17 | DurationType type = DurationType.daily;
18 | int number = 1;
19 |
20 | DurationData({
21 | required this.type,
22 | required this.number,
23 | });
24 |
25 | Map toJson() => {
26 | 'type': type.index,
27 | 'number': number,
28 | };
29 |
30 | factory DurationData.fromJson(Map json) => DurationData(
31 | type: DurationType.values[json['type']],
32 | number: json['number'],
33 | );
34 |
35 | @override
36 | String toString() {
37 | return 'DurationData(type: $type, number: $number)';
38 | }
39 |
40 | String toReadableString() {
41 | if (number == 1) {
42 | return translate(type.translationKey); // i.e. 'hour'
43 | } else {
44 | return translate('time.repetition.every.value', args: {'number': number, 'type': translate(type.sTranslationKey)}); // i.e. '2 hours'
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/noterly/lib/models/repetition_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_translate/flutter_translate.dart';
2 |
3 | enum Repetition {
4 | hourly('time.hourly', 'time.hours'),
5 | daily('time.daily', 'time.days'),
6 | weekly('time.weekly', 'time.weeks'),
7 | monthly('time.monthly', 'time.months'),
8 | yearly('time.yearly', 'time.years');
9 |
10 | final String lyTranslationKey;
11 | final String sTranslationKey;
12 |
13 | const Repetition(this.lyTranslationKey, this.sTranslationKey);
14 | }
15 |
16 | class RepetitionData {
17 | Repetition type = Repetition.daily;
18 | int number = 1;
19 |
20 | RepetitionData({
21 | required this.type,
22 | required this.number,
23 | });
24 |
25 | Map toJson() => {
26 | 'type': type.index,
27 | 'number': number,
28 | };
29 |
30 | factory RepetitionData.fromJson(Map json) => RepetitionData(
31 | type: Repetition.values[json['type']],
32 | number: json['number'],
33 | );
34 |
35 | @override
36 | String toString() {
37 | return 'RepetitionData(type: $type, number: $number)';
38 | }
39 |
40 | String toReadableString() {
41 | if (number == 1) {
42 | return translate(type.lyTranslationKey); // i.e. 'hourly'
43 | } else {
44 | return translate('time.repetition.every.value', args: {'number': number, 'type': translate(type.sTranslationKey)}); // i.e. 'every 2 hours'
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/noterly/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/noterly/lib/managers/file_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:io';
3 |
4 | import 'package:noterly/models/app_data.dart';
5 | import 'package:path_provider/path_provider.dart';
6 |
7 | import 'log.dart';
8 |
9 | class FileManager {
10 | static Future get _localPath async {
11 | final directory = await getApplicationDocumentsDirectory();
12 | return directory.path;
13 | }
14 |
15 | static Future get _localFile async {
16 | final path = await _localPath;
17 | return File('$path/data.json');
18 | }
19 |
20 | static Future save(AppData data) async {
21 | final file = await _localFile;
22 |
23 | file.writeAsString(jsonEncode(data.toJson()));
24 | }
25 |
26 | static Future load() async {
27 | try {
28 | final file = await _localFile;
29 |
30 | final contents = await file.readAsString();
31 |
32 | return AppData.fromJson(jsonDecode(contents));
33 | } catch (e) {
34 | Log.logger.d('No previous save found. Error $e');
35 | return null;
36 | }
37 | }
38 |
39 | static Future loadRaw() async {
40 | try {
41 | final file = await _localFile;
42 |
43 | final contents = await file.readAsString();
44 |
45 | return contents;
46 | } catch (e) {
47 | Log.logger.d('No previous save found. Error $e');
48 | return null;
49 | }
50 | }
51 |
52 | static Future delete() async {
53 | try {
54 | final file = await _localFile;
55 | await file.delete();
56 | } catch (e) {
57 | Log.logger.d('No previous save found. Error $e');
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/noterly/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.8.10' // WAS: 1.7.10
3 |
4 | ext {
5 | compileSdkVersion = 33
6 | targetSdkVersion = 33
7 | minSdkVersion = 23 // INFO: 19 for Firebase, 23 is minimum for getActiveNotifications() function
8 | appCompatVersion = "1.6.1" // INFO: Required for background_fetch plugin, was 1.4.2
9 | }
10 |
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 |
16 | dependencies {
17 | // WAS: 7.1.2, 7.2.2, 7.3.0
18 | // 7.3.0 and higher break
19 | // all 7.x versions cause issues with gradle in IntelliJ
20 | // therefore downgraded to 4.2.0, 4.2.2
21 | // UPDATE: 4.2.2 no longer worked, updating to 7.2.2 fixed it and seems to work (flutter upgrade fixed?)
22 | classpath 'com.android.tools.build:gradle:7.2.2'
23 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
24 |
25 | classpath 'com.google.gms:google-services:4.3.15' // INFO: Firebase manual installation
26 | }
27 | }
28 |
29 | allprojects {
30 | repositories {
31 | google()
32 | mavenCentral()
33 |
34 | // INFO: Required for background_fetch plugin
35 | maven {
36 | url "${project(':background_fetch').projectDir}/libs"
37 | }
38 | }
39 | }
40 |
41 | rootProject.buildDir = '../build'
42 | subprojects {
43 | project.buildDir = "${rootProject.buildDir}/${project.name}"
44 | }
45 | subprojects {
46 | project.evaluationDependsOn(':app')
47 | }
48 |
49 | tasks.register("clean", Delete) {
50 | delete rootProject.buildDir
51 | }
52 |
--------------------------------------------------------------------------------
/noterly/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: noterly
2 | description: Simple notification reminders.
3 |
4 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
5 |
6 | version: 0.0.1+1
7 |
8 | environment:
9 | sdk: '>=3.0.0 <4.0.0'
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 | path_provider: ^2.0.11
15 | intl: ^0.18.0
16 | uuid: ^3.0.7
17 | flutter_local_notifications: ^14.0.0
18 | timezone: ^0.9.1
19 | google_fonts: ^4.0.3
20 | dynamic_color: ^1.6.3
21 | jiffy: ^6.0.0
22 | background_fetch: ^1.1.3
23 | quick_actions: ^1.0.1
24 | url_launcher: ^6.1.8
25 | logger: ^1.1.0
26 | system_settings: ^2.1.0
27 | firebase_core: ^2.4.1
28 | firebase_analytics: ^10.1.0
29 | firebase_crashlytics: ^3.0.11
30 | flutter_localizations:
31 | sdk: flutter
32 | auto_size_text: ^3.0.0
33 | fluttertoast: ^8.2.1
34 | receive_sharing_intent: ^1.4.5
35 | receive_intent: ^0.2.3
36 | dots_indicator: ^3.0.0
37 | flutter_translate: ^4.0.4
38 |
39 | dev_dependencies:
40 | flutter_test:
41 | sdk: flutter
42 | flutter_lints: ^2.0.0
43 |
44 | flutter:
45 | uses-material-design: true
46 | assets:
47 | - assets/i18n/
48 | - assets/google_fonts/
49 |
50 |
51 | # IconKitchen
52 | # Old: https://icon.kitchen/i/H4sIAAAAAAAAA0WQMWvEMAyF%2F0rQfENnz6XblUKylQ66WHbE2ZaxlcIR7r9XTindpE967wkd8I1ppw7uAI%2FtvmyUCZy2nS4Q4vKo1gFnjAQDXLHf%2F8cf6D2XOMQqFdzLBRrHTc%2FqJqqSzzJRGOxp8M9ylSTNLG9x3vCXcFvTSNFxgwcXMHWLqf1NiskPCJg5PWz19TrNWDqYYe0Lq8kcvItSs%2FHJMCYug86ca6KpiHLgFZWlTI0yF0%2FtNMji9zQe8AlYfBP28PX8AdrnM%2FUXAQAA
53 |
54 | # https://icon.kitchen/i/H4sIAAAAAAAAA12OvQrDMAyE30Vzhs5%2Bh0Kh2UoHOfIfseNgy4US8u61TKdsp0P33R3wwdhMBXUAYVlnb5IBxaWZCaybv3u%2FICR0BsR4IFHYnLxz3kHdJijBeR5KZ%2BachozGindK5o51BWUx1s7U7ulxQJdQlihUfa1hGUH%2FSEekTC3KxhfgRiUHgvf5A0C99Nq6AAAA
55 | # Change foreground to the themed version to generate better themed icon
56 |
57 |
58 | # Flutter rename:
59 | # flutter pub global run rename --bundleId com.name.here
60 | # flutter pub global run rename --appname "Name Here"
--------------------------------------------------------------------------------
/website/scss/core/_reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | * {
7 | box-sizing: border-box;
8 | }
9 |
10 | // body {
11 | // overflow-x: hidden;
12 | // }
13 |
14 | html, body{
15 | overflow-x: hidden;
16 | }
17 |
18 | html,
19 | body,
20 | div,
21 | span,
22 | applet,
23 | object,
24 | iframe,
25 | h1,
26 | h2,
27 | h3,
28 | h4,
29 | h5,
30 | h6,
31 | p,
32 | blockquote,
33 | pre,
34 | a,
35 | abbr,
36 | acronym,
37 | address,
38 | big,
39 | cite,
40 | code,
41 | del,
42 | dfn,
43 | em,
44 | img,
45 | ins,
46 | kbd,
47 | q,
48 | s,
49 | samp,
50 | small,
51 | strike,
52 | strong,
53 | sub,
54 | sup,
55 | tt,
56 | var,
57 | b,
58 | u,
59 | i,
60 | center,
61 | dl,
62 | dt,
63 | dd,
64 | ol,
65 | ul,
66 | li,
67 | fieldset,
68 | form,
69 | label,
70 | legend,
71 | table,
72 | caption,
73 | tbody,
74 | tfoot,
75 | thead,
76 | tr,
77 | th,
78 | td,
79 | article,
80 | aside,
81 | canvas,
82 | details,
83 | embed,
84 | figure,
85 | figcaption,
86 | footer,
87 | header,
88 | hgroup,
89 | menu,
90 | nav,
91 | output,
92 | ruby,
93 | section,
94 | summary,
95 | time,
96 | mark,
97 | audio,
98 | video {
99 | margin: 0;
100 | padding: 0;
101 | border: 0;
102 | vertical-align: baseline;
103 | }
104 | /* HTML5 display-role reset for older browsers */
105 | article,
106 | aside,
107 | details,
108 | figcaption,
109 | figure,
110 | footer,
111 | header,
112 | hgroup,
113 | menu,
114 | nav,
115 | section {
116 | display: block;
117 | }
118 | body {
119 | line-height: 1;
120 | }
121 | ol,
122 | ul {
123 | list-style: none;
124 | }
125 | blockquote,
126 | q {
127 | quotes: none;
128 | }
129 | blockquote:before,
130 | blockquote:after,
131 | q:before,
132 | q:after {
133 | content: '';
134 | content: none;
135 | }
136 | table {
137 | border-collapse: collapse;
138 | border-spacing: 0;
139 | }
140 |
--------------------------------------------------------------------------------
/noterly/lib/models/app_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:noterly/models/notification_item.dart';
2 |
3 | class AppData {
4 | List notificationItems;
5 |
6 | Duration snoozeDuration;
7 | String snoozeToastText;
8 |
9 | int firstLaunchDialogLastShown;
10 |
11 | AppData({
12 | required this.notificationItems,
13 | required this.snoozeDuration,
14 | required this.snoozeToastText,
15 | required this.firstLaunchDialogLastShown,
16 | });
17 |
18 | Map toJson() {
19 | return {
20 | 'notificationItems': notificationItems.map((e) => e.toJson()).toList(),
21 | 'snoozeDuration': snoozeDuration.inSeconds,
22 | 'snoozeToastText': snoozeToastText,
23 | 'firstLaunchDialogLastShown': firstLaunchDialogLastShown,
24 | };
25 | }
26 |
27 | factory AppData.defaults() => AppData.fromJson({});
28 |
29 | factory AppData.fromJson(Map json) => AppData(
30 | notificationItems: AppDataJsonParser>('notificationItems', [], parser: (value) => (value as List).map((item) => NotificationItem.fromJson(item)).toList()).parse(json),
31 | snoozeDuration: AppDataJsonParser('snoozeDuration', const Duration(hours: 1), parser: (value) => Duration(seconds: value)).parse(json),
32 | snoozeToastText: AppDataJsonParser('snoozeToastText', 'Notification snoozed').parse(json),
33 | firstLaunchDialogLastShown: AppDataJsonParser('firstLaunchDialogLastShown', -1).parse(json),
34 | );
35 |
36 | @override
37 | String toString() => 'AppData(notificationItems: $notificationItems, snoozeDuration: $snoozeDuration, snoozeToastText: $snoozeToastText, firstLaunchDialogLastShown: $firstLaunchDialogLastShown)';
38 | }
39 |
40 | class AppDataJsonParser {
41 | final String key;
42 | final T defaultValue;
43 | final T Function(dynamic)? parser;
44 |
45 | AppDataJsonParser(this.key, this.defaultValue, {this.parser});
46 |
47 | T parse(Map json) {
48 | if (!json.containsKey(key)) return defaultValue;
49 | if (parser != null) return parser!(json[key]);
50 | return json[key];
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # Noterly
5 | Simple notification reminders for Android devices.
6 |
7 |
8 | ## 📸 Screenshots
9 |
10 | 
11 |
12 |
13 | ## 🗞️ Newly Open-Sourced
14 |
15 | Noterly is undergoing a transition from being closed-source to open-source. Please hang tight whilst I set up the repository's open source features (such as issues and pull requests).
16 |
17 |
18 | ## 👇🏻 Download
19 |
20 | Noterly is available directly on the [Play Store](https://play.google.com/store/apps/details?id=uk.co.tdsstudios.noterly).
21 |
22 | Alternatively, APKs will soon be available directly on the GitHub releases page (keep an eye out!).
23 |
24 |
25 | ## 📝 Features
26 |
27 | - ✏️ Create reminders with a title and body text
28 | - ⌚ Send reminders instantly, or schedule them for a specific time
29 | - 🪃 Set reminders to repeat with customisable intervals
30 | - 💤 Snooze reminders for a customisable amount of time
31 | - 🏃🏻♀️ Create reminders from a quick settings tile, or share text to Noterly from other apps
32 | - 🎨 Simple Material You UI
33 |
34 |
35 | ## 🔗 Links
36 |
37 | - Get Noterly on the [Play Store](https://play.google.com/store/apps/details?id=uk.co.tdsstudios.noterly)
38 | - Check out [Noterly's landing page](https://noterly.tdsstudios.co.uk) on the TDS Studios website
39 | - Watch the [YouTube video](https://youtu.be/7qwUOWT9QbA) on how I created Noterly
40 | - Visit my [personal website](https://www.tomchapman.dev) to see what else I'm working on
41 | - If you need, [Noterly's privacy policy](https://www.tdsstudios.co.uk/privacy#noterly) is available on the TDS Studios website
42 |
43 |
44 | ## 🌍 Translations
45 |
46 | Noterly has now switched to Crowdin to provide translations. You can find the [Crowdin project here](https://crowdin.com/project/noterly). Feel free to contribute!
47 |
48 | Noterly is currently available in the following languages:
49 |
50 | - English (GB and US)
51 | - French
52 | - Spanish
53 | - German
54 | - Russian
55 | - Ukrainian
56 | - Polish
57 | - Italian
58 |
59 |
60 | ## ⚠️ Issues
61 |
62 | If you find any bugs or issues, please [open an issue](https://github.com/tomc128/noterly/issues/new) on GitHub.
63 |
64 | Familiar with Flutter? Feel free to submit a pull request!
65 |
--------------------------------------------------------------------------------
/noterly/lib/firebase_options.dart:
--------------------------------------------------------------------------------
1 | // File generated by FlutterFire CLI.
2 | // ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
3 | import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
4 | import 'package:flutter/foundation.dart'
5 | show defaultTargetPlatform, kIsWeb, TargetPlatform;
6 |
7 | /// Default [FirebaseOptions] for use with your Firebase apps.
8 | ///
9 | /// Example:
10 | /// ```dart
11 | /// import 'firebase_options.dart';
12 | /// // ...
13 | /// await Firebase.initializeApp(
14 | /// options: DefaultFirebaseOptions.currentPlatform,
15 | /// );
16 | /// ```
17 | class DefaultFirebaseOptions {
18 | static FirebaseOptions get currentPlatform {
19 | if (kIsWeb) {
20 | throw UnsupportedError(
21 | 'DefaultFirebaseOptions have not been configured for web - '
22 | 'you can reconfigure this by running the FlutterFire CLI again.',
23 | );
24 | }
25 | switch (defaultTargetPlatform) {
26 | case TargetPlatform.android:
27 | return android;
28 | case TargetPlatform.iOS:
29 | throw UnsupportedError(
30 | 'DefaultFirebaseOptions have not been configured for ios - '
31 | 'you can reconfigure this by running the FlutterFire CLI again.',
32 | );
33 | case TargetPlatform.macOS:
34 | throw UnsupportedError(
35 | 'DefaultFirebaseOptions have not been configured for macos - '
36 | 'you can reconfigure this by running the FlutterFire CLI again.',
37 | );
38 | case TargetPlatform.windows:
39 | throw UnsupportedError(
40 | 'DefaultFirebaseOptions have not been configured for windows - '
41 | 'you can reconfigure this by running the FlutterFire CLI again.',
42 | );
43 | case TargetPlatform.linux:
44 | throw UnsupportedError(
45 | 'DefaultFirebaseOptions have not been configured for linux - '
46 | 'you can reconfigure this by running the FlutterFire CLI again.',
47 | );
48 | default:
49 | throw UnsupportedError(
50 | 'DefaultFirebaseOptions are not supported for this platform.',
51 | );
52 | }
53 | }
54 |
55 | static const FirebaseOptions android = FirebaseOptions(
56 | apiKey: 'AIzaSyDUc9aCGvB4liffuR-8msAHn4HYkLLUc4M',
57 | appId: '1:785359253319:android:5f2b4bcc833a715698a662',
58 | messagingSenderId: '785359253319',
59 | projectId: 'noterly',
60 | storageBucket: 'noterly.appspot.com',
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/noterly/android/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "785359253319",
4 | "project_id": "noterly",
5 | "storage_bucket": "noterly.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:785359253319:android:36347bef6f0ccd8998a662",
11 | "android_client_info": {
12 | "package_name": "com.example.noterly"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "785359253319-loimnkr2h8mc73u9jntcinvontcbdmqh.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyDUc9aCGvB4liffuR-8msAHn4HYkLLUc4M"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "785359253319-loimnkr2h8mc73u9jntcinvontcbdmqh.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | },
37 | {
38 | "client_info": {
39 | "mobilesdk_app_id": "1:785359253319:android:5f2b4bcc833a715698a662",
40 | "android_client_info": {
41 | "package_name": "uk.co.tdsstudios.noterly"
42 | }
43 | },
44 | "oauth_client": [
45 | {
46 | "client_id": "785359253319-r7g7n61qmhiqaf2gupb2dr35jkj0juau.apps.googleusercontent.com",
47 | "client_type": 1,
48 | "android_info": {
49 | "package_name": "uk.co.tdsstudios.noterly",
50 | "certificate_hash": "b9594c460bebce5c203204ac5e291aa562b7ac8e"
51 | }
52 | },
53 | {
54 | "client_id": "785359253319-vf5qncgvetmck31gidlm0742eql3gn07.apps.googleusercontent.com",
55 | "client_type": 1,
56 | "android_info": {
57 | "package_name": "uk.co.tdsstudios.noterly",
58 | "certificate_hash": "79ff8b42993a16829d6c9df2a2c205b5ace0a8e5"
59 | }
60 | },
61 | {
62 | "client_id": "785359253319-loimnkr2h8mc73u9jntcinvontcbdmqh.apps.googleusercontent.com",
63 | "client_type": 3
64 | }
65 | ],
66 | "api_key": [
67 | {
68 | "current_key": "AIzaSyDUc9aCGvB4liffuR-8msAHn4HYkLLUc4M"
69 | }
70 | ],
71 | "services": {
72 | "appinvite_service": {
73 | "other_platform_oauth_client": [
74 | {
75 | "client_id": "785359253319-loimnkr2h8mc73u9jntcinvontcbdmqh.apps.googleusercontent.com",
76 | "client_type": 3
77 | }
78 | ]
79 | }
80 | }
81 | }
82 | ],
83 | "configuration_version": "1"
84 | }
--------------------------------------------------------------------------------
/website/scss/sections/_footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | #footer-background {
3 | z-index: 10;
4 | width: 100vw;
5 | height: $footer-height;
6 | filter: $large-shadow-drop-shadow;
7 | }
8 |
9 |
10 | .background {
11 | width: 100vw;
12 | filter: $large-shadow-drop-shadow;
13 |
14 | >* {
15 | width: 100%;
16 | background: $dark-gradient;
17 |
18 | // TODO: Adding the transform line greatly improves performance,...
19 | // but also breaks the background attachment. Need to fix this as
20 | // the gradient is not fixed to the viewport.
21 | background-attachment: fixed;
22 | transform: translateZ(0); //
23 | }
24 |
25 | .background-waves {
26 | height: $wave-height;
27 | clip-path: url(#waves-bottom-clip);
28 |
29 | @include display-medium {
30 | clip-path: url(#waves-bottom-clip-mobile);
31 | }
32 | }
33 |
34 | .background-fill {
35 |
36 | height: 25rem;
37 | }
38 | }
39 |
40 | position: relative;
41 | margin-top: -$wave-height;
42 | z-index: 20;
43 |
44 | .content {
45 | z-index: 100;
46 | position: absolute;
47 | top: $wave-height - 5rem;
48 | width: 100vw;
49 |
50 | h2 {
51 | color: $text-primary-title;
52 | text-shadow: $large-shadow;
53 | }
54 |
55 | p {
56 | color: $text-primary;
57 | font-weight: 400;
58 | }
59 |
60 | .footer-grid {
61 | width: 100%;
62 | display: flex;
63 | justify-content: space-between;
64 | margin-top: 1rem;
65 | gap: 1rem;
66 |
67 | flex-direction: row;
68 |
69 | @include display-small {
70 | flex-direction: column;
71 | }
72 |
73 | .column {
74 | display: flex;
75 | flex-direction: column;
76 |
77 | hr {
78 | max-width: 100px;
79 | width: 100%;
80 | border-top: 1px solid $divider;
81 | border-right: none;
82 | border-bottom: none;
83 | border-left: none;
84 | }
85 |
86 | &.text {
87 | gap: 0.5rem;
88 | }
89 |
90 | &.links {
91 | gap: 1rem;
92 |
93 | a {
94 | display: inline-flex;
95 | align-items: center;
96 | gap: 0.5rem;
97 | color: $text-primary;
98 | text-decoration: none;
99 |
100 | &:hover {
101 | text-decoration: underline;
102 | }
103 | }
104 | }
105 | }
106 | }
107 | }
108 | }
--------------------------------------------------------------------------------
/noterly/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "Build AppBundle",
8 | "type": "shell",
9 | "command": [
10 | "(Get-Content ./lib/build_info.dart) | ",
11 | "Foreach-Object {$_ -replace 'UNSET_VERSION','${input:versionName}'} | ",
12 | "Out-File ./lib/build_info.dart",
13 | "&&",
14 | "(Get-Content ./lib/build_info.dart) | ",
15 | "Foreach-Object {$_ -replace 'ReleaseType.inDev','ReleaseType.${input:releaseType}'} | ",
16 | "Out-File ./lib/build_info.dart",
17 | "&&",
18 | "(Get-Content ./lib/build_info.dart) | ",
19 | "Foreach-Object {$_ -replace 'UNSET_BRANCH',(git branch --show-current)} | ",
20 | "Out-File ./lib/build_info.dart",
21 | "&&",
22 | "(Get-Content ./lib/build_info.dart) | ",
23 | "Foreach-Object {$_ -replace '-80085','${input:versionCode}'} | ",
24 | "Out-File ./lib/build_info.dart",
25 | "&&",
26 | "flutter",
27 | "build",
28 | "appbundle",
29 | "--build-name=${input:versionName}",
30 | "--build-number=${input:versionCode}",
31 | "--split-debug-info=build/app/outputs/symbols",
32 | "&&",
33 | "(Get-Content ./lib/build_info.dart) | ",
34 | "Foreach-Object {$_ -replace '${input:versionName}','UNSET_VERSION'} | ",
35 | "Out-File ./lib/build_info.dart",
36 | "&&",
37 | "(Get-Content ./lib/build_info.dart) | ",
38 | "Foreach-Object {$_ -replace 'ReleaseType.${input:releaseType}','ReleaseType.inDev'} | ",
39 | "Out-File ./lib/build_info.dart",
40 | "&&",
41 | "(Get-Content ./lib/build_info.dart) | ",
42 | "Foreach-Object {$_ -replace (git branch --show-current),'UNSET_BRANCH'} | ",
43 | "Out-File ./lib/build_info.dart",
44 | "&&",
45 | "(Get-Content ./lib/build_info.dart) | ",
46 | "Foreach-Object {$_ -replace '${input:versionCode}','-80085'} | ",
47 | "Out-File ./lib/build_info.dart",
48 | ]
49 | }
50 | ],
51 | "inputs": [
52 | {
53 | "id": "versionName",
54 | "type": "promptString",
55 | "description": "Set the version name, i.e. '1.0.0'",
56 | },
57 | {
58 | "id": "versionCode",
59 | "type": "promptString",
60 | "description": "Set the version code, i.e. '1'",
61 | },
62 | {
63 | "id": "releaseType",
64 | "type": "pickString",
65 | "description": "Set the release type",
66 | "options": [
67 | "stable",
68 | "beta",
69 | "personalTest",
70 | ]
71 | }
72 | ]
73 | }
--------------------------------------------------------------------------------
/noterly/lib/extensions/date_time_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_translate/flutter_translate.dart';
2 | import 'package:intl/intl.dart';
3 | import 'package:jiffy/jiffy.dart';
4 |
5 | extension DateTimeExtensions on DateTime {
6 | bool isToday() => DateTime.now().year == year && DateTime.now().month == month && DateTime.now().day == day;
7 |
8 | String toDateOnlyString() => DateFormat.MMMMEEEEd().format(this);
9 |
10 | String toTimeOnlyString() => DateFormat.jm().format(this);
11 |
12 | String toDateTimeString() => DateFormat.MMMMEEEEd().add_jm().format(this);
13 |
14 | String toDateTimeWithYearString() => DateFormat.yMMMMEEEEd().add_jm().format(this);
15 |
16 | String toRelativeDateTimeString({bool alwaysShowDay = false}) {
17 | final now = DateTime.now();
18 |
19 | bool isToday() => now.year == year && now.month == month && now.day == day;
20 | bool isYesterday() => now.year == year && now.month == month && now.day - day == 1;
21 | bool isTomorrow() => now.year == year && now.month == month && now.day - day == -1;
22 | bool isBeforeNextWeek() => isBefore(now.add(const Duration(days: 7)));
23 |
24 | if (isToday()) return alwaysShowDay ? translate('time.today_and_time', args: {'time': toTimeOnlyString()}) : Jiffy.parseFromDateTime(this).fromNow();
25 |
26 | if (isYesterday()) return translate('time.yesterday_and_time', args: {'time': toTimeOnlyString()});
27 |
28 | if (isTomorrow()) return translate('time.tomorrow_and_time', args: {'time': toTimeOnlyString()});
29 |
30 | if (isBeforeNextWeek()) return translate('time.date_and_time', args: {'date': DateFormat.EEEE().format(this), 'time': toTimeOnlyString()});
31 |
32 | if (year == now.year) return toDateTimeString();
33 |
34 | return toDateTimeWithYearString();
35 | }
36 |
37 | String toSnoozedUntilDateTimeString() {
38 | final now = DateTime.now();
39 |
40 | bool isToday() => now.year == year && now.month == month && now.day == day;
41 | bool isYesterday() => now.year == year && now.month == month && now.day - day == 1;
42 | bool isTomorrow() => now.year == year && now.month == month && now.day - day == -1;
43 | bool isBeforeNextWeek() => isBefore(now.add(const Duration(days: 7)));
44 |
45 | if (isToday()) return translate('time.snooze.until', args: {'date_time': toTimeOnlyString()});
46 |
47 | if (isYesterday()) {
48 | return translate('time.snooze.until', args: {
49 | 'date_time': translate('time.yesterday_and_time', args: {'time': toTimeOnlyString()})
50 | });
51 | }
52 |
53 | if (isTomorrow()) {
54 | return translate('time.snooze.until', args: {
55 | 'date_time': translate('time.tomorrow_and_time', args: {'time': toTimeOnlyString()})
56 | });
57 | }
58 |
59 | if (isBeforeNextWeek()) {
60 | return translate('time.snooze.until', args: {
61 | 'date_time': translate('time.date_and_time', args: {'date': DateFormat.EEEE().format(this), 'time': toTimeOnlyString()})
62 | });
63 | }
64 |
65 | if (year == now.year) return translate('time.snooze.until', args: {'date_time': toDateTimeString()});
66 |
67 | return toDateTimeWithYearString();
68 | }
69 |
70 | DateTime toOnlyDate() => DateTime(year, month, day);
71 | }
72 |
--------------------------------------------------------------------------------
/website/scss/sections/_hero.scss:
--------------------------------------------------------------------------------
1 | #hero {
2 | min-height: 100vh;
3 |
4 | .background {
5 | width: 100vw;
6 | filter: $large-shadow-drop-shadow;
7 |
8 | >* {
9 | width: 100%;
10 | background: $dark-gradient;
11 |
12 | // TODO: Adding the transform line greatly improves performance,...
13 | // but also breaks the background attachment. Need to fix this as
14 | // the gradient is not fixed to the viewport.
15 | background-attachment: fixed;
16 | transform: translateZ(0); // I think this forces GPU rendering, without it its extremely slow on mobile especially
17 | }
18 |
19 | .background-fill {
20 | min-height: 100vh;
21 | }
22 |
23 | .background-waves {
24 | height: $wave-height;
25 | clip-path: url(#waves-top-clip);
26 | @include display-medium {
27 | clip-path: url(#waves-top-clip-mobile);
28 | }
29 | }
30 | }
31 |
32 | z-index: 20;
33 |
34 | .content {
35 | z-index: 100;
36 | position: absolute;
37 | top: 0;
38 | width: 100%;
39 | height: calc(100% - $wave-height);
40 |
41 | display: flex;
42 | justify-content: center;
43 | gap: 1rem;
44 | flex-direction: column;
45 |
46 | // On small to medium screens, the screenshot is below the text
47 | @include display-medium {
48 | align-items: center;
49 |
50 | h1, h2 {
51 | text-align: center;
52 | }
53 | }
54 |
55 | h1 {
56 | color: $text-primary-title;
57 | font-size: $hero-title-size;
58 | text-shadow: $large-shadow;
59 | }
60 |
61 | h2 {
62 | color: $text-primary;
63 | font-size: $hero-subtitle-size;
64 | font-weight: 400;
65 | }
66 |
67 | .logo {
68 | position: relative;
69 | width: $logo-size;
70 | height: $logo-size;
71 | margin-bottom: 1rem;
72 |
73 | border-radius: $logo-border-radius;
74 | box-shadow: $large-shadow;
75 |
76 | .foreground,
77 | .background {
78 | width: 100%;
79 | position: absolute;
80 | border-radius: $logo-border-radius;
81 | }
82 | }
83 |
84 | .buttons {
85 | margin-top: 1rem;
86 | }
87 |
88 | .screenshot {
89 | position: absolute;
90 | right: $side-padding;
91 |
92 | @include display-medium {
93 | position: relative;
94 | right: 0;
95 | margin-top: 3rem;
96 | }
97 |
98 | img {
99 | height: 60vh;
100 | max-width: 100vw;
101 | object-fit: contain;
102 |
103 | border-radius: $border-radius;
104 | box-shadow: $large-shadow;
105 |
106 | @include display-medium {
107 | height: 40vh;
108 | }
109 |
110 | @include display-xxsmall {
111 | height: auto;
112 | width: 70vw;
113 | }
114 | }
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/assets/figma/logo_foreground.svg:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/website/img/logo/logo_foreground.svg:
--------------------------------------------------------------------------------
1 |
51 |
--------------------------------------------------------------------------------
/noterly/lib/models/notification_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:noterly/models/repetition_data.dart';
3 |
4 | class NotificationItem {
5 | String id;
6 |
7 | String title;
8 | String body;
9 |
10 | Color colour;
11 |
12 | DateTime? dateTime;
13 | RepetitionData? repetitionData;
14 |
15 | bool archived;
16 | DateTime? archivedDateTime;
17 |
18 | DateTime? snoozeDateTime;
19 |
20 | bool get isImmediate => dateTime == null;
21 |
22 | bool get isScheduled => dateTime != null;
23 |
24 | bool get isRepeating => repetitionData != null;
25 |
26 | bool get isNotRepeating => repetitionData == null;
27 |
28 | bool get isSnoozed => snoozeDateTime != null;
29 |
30 | bool get isNotSnoozed => snoozeDateTime == null;
31 |
32 | bool get isSnoozedPast => snoozeDateTime != null && snoozeDateTime!.isBefore(DateTime.now());
33 |
34 | bool get isSnoozedFuture => snoozeDateTime != null && snoozeDateTime!.isAfter(DateTime.now());
35 |
36 | NotificationItem({
37 | required this.id,
38 | required this.title,
39 | required this.body,
40 | this.dateTime,
41 | this.repetitionData,
42 | required this.colour,
43 | this.archived = false,
44 | this.archivedDateTime,
45 | this.snoozeDateTime,
46 | });
47 |
48 | @override
49 | String toString() {
50 | return 'NotificationItem(id: $id, title: $title, body: $body, dateTime: $dateTime, repetitionData: $repetitionData, colour: $colour, archived: $archived, archivedDateTime: $archivedDateTime, snoozeDateTime: $snoozeDateTime)';
51 | }
52 |
53 | DateTime get nextRepeatDateTime {
54 | if (repetitionData == null) return DateTime.now();
55 |
56 | final now = DateTime.now();
57 | final lastSent = dateTime ?? now;
58 |
59 | // Return the datetime at which the notification should be sent next. Base it off the last time it was sent.
60 |
61 | switch (repetitionData!.type) {
62 | case Repetition.hourly:
63 | return lastSent.add(Duration(hours: repetitionData!.number));
64 | case Repetition.daily:
65 | return lastSent.add(Duration(days: repetitionData!.number));
66 | case Repetition.weekly:
67 | return lastSent.add(Duration(days: repetitionData!.number * 7));
68 | case Repetition.monthly:
69 | return DateTime(lastSent.year, lastSent.month + repetitionData!.number, lastSent.day);
70 | case Repetition.yearly:
71 | return DateTime(lastSent.year + repetitionData!.number, lastSent.month, lastSent.day);
72 | }
73 | }
74 |
75 | Map toJson() => {
76 | 'id': id,
77 | 'title': title,
78 | 'body': body,
79 | 'dateTime': dateTime?.toIso8601String(),
80 | 'repetitionData': repetitionData?.toJson(),
81 | 'colour': colour.value,
82 | 'archived': archived,
83 | 'archivedDateTime': archivedDateTime?.toIso8601String(),
84 | 'snoozeDateTime': snoozeDateTime?.toIso8601String(),
85 | };
86 |
87 | factory NotificationItem.fromJson(Map json) => NotificationItem(
88 | id: json['id'],
89 | title: json['title'],
90 | body: json['body'],
91 | dateTime: json['dateTime'] != null ? DateTime.parse(json['dateTime']) : null,
92 | repetitionData: json['repetitionData'] != null ? RepetitionData.fromJson(json['repetitionData']) : null,
93 | colour: Color(json['colour']),
94 | archived: json['archived'] ?? false,
95 | archivedDateTime: json['archivedDateTime'] != null ? DateTime.parse(json['archivedDateTime']) : null,
96 | snoozeDateTime: json['snoozeDateTime'] != null ? DateTime.parse(json['snoozeDateTime']) : null,
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/assets/figma/logo_full.svg:
--------------------------------------------------------------------------------
1 |
56 |
--------------------------------------------------------------------------------
/assets/figma/logo_full_circle.svg:
--------------------------------------------------------------------------------
1 |
56 |
--------------------------------------------------------------------------------
/assets/figma/logo_full_rounded.svg:
--------------------------------------------------------------------------------
1 |
56 |
--------------------------------------------------------------------------------
/noterly/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: 'com.google.gms.google-services' // INFO: Firebase manual installation
26 | apply plugin: 'kotlin-android'
27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
28 |
29 | // INFO: app signing and keystore
30 | def keystoreProperties = new Properties()
31 | def keystorePropertiesFile = rootProject.file('key.properties')
32 | if (keystorePropertiesFile.exists()) {
33 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
34 | }
35 |
36 | android {
37 | compileSdkVersion rootProject.ext.compileSdkVersion // WAS: flutter.compileSdkVersion
38 | ndkVersion flutter.ndkVersion
39 |
40 | compileOptions {
41 | // INFO: required for flutter_local_notifications plugin, when minSdkVersion < 20
42 | // INFO: required for Firebase
43 | coreLibraryDesugaringEnabled true
44 |
45 | sourceCompatibility JavaVersion.VERSION_1_8
46 | targetCompatibility JavaVersion.VERSION_1_8
47 | }
48 |
49 | kotlinOptions {
50 | jvmTarget = '1.8'
51 | }
52 |
53 | sourceSets {
54 | main.java.srcDirs += 'src/main/kotlin'
55 | }
56 |
57 | defaultConfig {
58 | applicationId "uk.co.tdsstudios.noterly"
59 |
60 | // INFO: needed to prevent desugaring error for flutter_local_notifications plugin
61 | // INFO: required for Firebase
62 | multiDexEnabled true
63 |
64 | minSdkVersion rootProject.ext.minSdkVersion // WAS: flutter.minSdkVersion
65 | targetSdkVersion rootProject.ext.targetSdkVersion // WAS: flutter.targetSdkVersion
66 | versionCode flutterVersionCode.toInteger()
67 | versionName flutterVersionName
68 |
69 | // INFO: this supposedly helps stop the crash due to "could not find libflutter.so", this excludes 'x86' (32-bit)
70 | ndk {
71 | abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
72 | }
73 | }
74 |
75 | signingConfigs {
76 | release {
77 | keyAlias keystoreProperties['keyAlias']
78 | keyPassword keystoreProperties['keyPassword']
79 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
80 | storePassword keystoreProperties['storePassword']
81 | }
82 | }
83 | buildTypes {
84 | release {
85 | // INFO: testing to try and fix splash screen hang issue
86 | minifyEnabled true
87 | shrinkResources false // INFO: unsure if this needs to be false or not
88 |
89 | signingConfig signingConfigs.release
90 | }
91 | }
92 | }
93 |
94 | flutter {
95 | source '../..'
96 | }
97 |
98 | dependencies {
99 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
100 |
101 | // INFO: required for Firebase
102 | implementation 'com.android.support:multidex:1.0.3'
103 |
104 | // INFO: needed to prevent desugaring error for flutter_local_notifications plugin, 1.1.5 is the LAST VERSION that works
105 | // noinspection GradleDependency
106 | coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
107 |
108 | // INFO: needed to prevent Android 12L crash for flutter_local_notifications plugin
109 | implementation 'androidx.window:window:1.0.0'
110 | implementation 'androidx.window:window-java:1.0.0'
111 | }
112 |
--------------------------------------------------------------------------------
/noterly/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
30 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
66 |
67 |
68 |
71 |
72 |
73 |
74 |
81 |
82 |
83 |
85 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/scripts/i18n/i18n.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import shutil
4 |
5 | import gspread
6 | from rich import print
7 |
8 | # The directory to save the parsed language files to.
9 | OUTPUT_DIRECTORY = './out'
10 |
11 | # The directory to save the parsed language files to in the Flutter project,
12 | # if SAVE_TO_FLUTTER is set to True.
13 | FLUTTER_OUTPUT_DIRECTORY = '../../noterly/assets/i18n'
14 |
15 | # If set to True, the output will be also saved directly to
16 | # FLUTTER_OUTPUT_DIRECTORY as well as OUTPUT_DIRECTORY.
17 | SAVE_TO_FLUTTER = True
18 |
19 | # If set to True, the output directory will be purged before
20 | # generating the new files. This helps to remove any old files
21 | # that are no longer needed.
22 | PURGE_DIRECTORY = True
23 |
24 | # If set to True, the "$include" key will be used to determine
25 | # which languages to include in the output.
26 | PARSE_INCLUDE = True
27 |
28 | # If set to True, any missing translations will be filled with
29 | # the English translation.
30 | FILL_MISSING_TRANSLATIONS = False
31 |
32 |
33 | print('[bold]i18n script[/bold]')
34 | print(f'OUTPUT_DIRECTORY: [italic]{OUTPUT_DIRECTORY}[/italic]')
35 | print(f'FLUTTER_OUTPUT_DIRECTORY: [italic]{FLUTTER_OUTPUT_DIRECTORY}[/italic]')
36 | print(f'SAVE_TO_FLUTTER: [italic]{SAVE_TO_FLUTTER}[/italic]')
37 | print(f'PURGE_DIRECTORY: [italic]{PURGE_DIRECTORY}[/italic]')
38 | print(f'PARSE_INCLUDE: [italic]{PARSE_INCLUDE}[/italic]')
39 | print(f'FILL_MISSING_TRANSLATIONS: [italic]{FILL_MISSING_TRANSLATIONS}[/italic]')
40 | print()
41 |
42 |
43 | print('[blue]Loading spreadsheet...[/blue]', end='')
44 | gc = gspread.service_account(filename='./credentials.json')
45 | sheet = gc.open_by_key('1c0I6lARH-S2x8sxpA2nQ1gZZm4Q6b5OyuPjVvjuvF94').worksheet('Translations')
46 | print(' [bold green]Done![/bold green]')
47 |
48 | print('[blue]Loading data...[/blue]', end='')
49 | data = sheet.get_values()
50 | print(' [bold green]Done![/bold green]')
51 |
52 |
53 | languages = [lang for lang in data[0][1:] if lang.strip() != '']
54 |
55 | translation_keys = [key[0] for key in data[1:] if
56 | key[0].strip() != '' and
57 | not key[0].strip().startswith('_') and
58 | not key[0].strip().startswith('$')]
59 |
60 | num_total_translations = len(translation_keys)
61 |
62 |
63 | if PURGE_DIRECTORY:
64 | if os.path.exists(OUTPUT_DIRECTORY):
65 | shutil.rmtree(OUTPUT_DIRECTORY)
66 | os.mkdir(OUTPUT_DIRECTORY)
67 |
68 | if SAVE_TO_FLUTTER:
69 | if os.path.exists(FLUTTER_OUTPUT_DIRECTORY):
70 | shutil.rmtree(FLUTTER_OUTPUT_DIRECTORY)
71 | os.mkdir(FLUTTER_OUTPUT_DIRECTORY)
72 |
73 | english_dict = {}
74 |
75 | print()
76 | for lang in languages:
77 | if PARSE_INCLUDE:
78 | included = data[1][languages.index(lang)+1].strip() == 'TRUE'
79 | if not included:
80 | print(f'[yellow]Skipping {lang}[/yellow]')
81 | continue
82 |
83 | print(f'[blue]Parsing {lang}...[blue]', end='')
84 |
85 | lang_dict = {}
86 | for key in translation_keys:
87 | for row in data[1:]:
88 | if row[0] == key:
89 | translation = row[languages.index(lang)+1].strip()
90 | if translation:
91 | lang_dict[key] = translation
92 |
93 | if lang == 'en_GB':
94 | english_dict = lang_dict
95 |
96 | num_translations = len(lang_dict)
97 | missing_translations = num_total_translations - num_translations
98 |
99 | if FILL_MISSING_TRANSLATIONS:
100 | for key in translation_keys:
101 | if key not in lang_dict:
102 | lang_dict[key] = english_dict[key]
103 |
104 | with open(f'{OUTPUT_DIRECTORY}/{lang}.json', 'w', encoding='utf-8') as f:
105 | json.dump(lang_dict, f)
106 |
107 | if SAVE_TO_FLUTTER:
108 | with open(f'{FLUTTER_OUTPUT_DIRECTORY}/{lang}.json', 'w', encoding='utf-8') as f:
109 | json.dump(lang_dict, f)
110 |
111 | print(' [green bold]Done![/green bold]', end='')
112 |
113 | print(f' [cyan]({num_translations}/{num_total_translations} translations)[/cyan]', end='')
114 |
115 | if missing_translations > 0:
116 | print(f' [red]({missing_translations} missing)[/red]', end='')
117 |
118 | if FILL_MISSING_TRANSLATIONS and missing_translations > 0:
119 | print(f' [yellow]({missing_translations} filled from en_GB)[/yellow]', end='')
120 |
121 | print()
122 |
123 | print('\n[green bold]All done![/green bold]')
124 |
--------------------------------------------------------------------------------
/noterly/assets/google_fonts/OFL.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014-2017 Indian Type Foundry (info@indiantypefoundry.com). Copyright 2019 Google LLC.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/noterly/lib/widgets/colour_picker.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_translate/flutter_translate.dart';
3 |
4 | Future showColourPicker({
5 | required BuildContext context,
6 | required Color initialColour,
7 | }) async {
8 | var dialog = ColourPicker(
9 | initialColour: initialColour,
10 | );
11 |
12 | return showDialog(
13 | context: context,
14 | builder: (context) => dialog,
15 | );
16 | }
17 |
18 | Map colours = {
19 | 'red': Colors.red,
20 | 'green': Colors.green,
21 | 'blue': Colors.blue,
22 | 'purple': Colors.purple,
23 | 'pink': Colors.pink,
24 | 'orange': Colors.orange,
25 | 'yellow': Colors.yellow,
26 | 'teal': Colors.teal,
27 | 'cyan': Colors.cyan,
28 | 'indigo': Colors.indigo,
29 | 'brown': Colors.brown,
30 | 'grey': Colors.grey,
31 | };
32 |
33 | class ColourPicker extends StatefulWidget {
34 | static const Map colours = {
35 | 'red': Colors.red,
36 | 'green': Colors.green,
37 | 'blue': Colors.blue,
38 | 'purple': Colors.purple,
39 | 'pink': Colors.pink,
40 | 'orange': Colors.orange,
41 | 'yellow': Colors.yellow,
42 | 'teal': Colors.teal,
43 | 'cyan': Colors.cyan,
44 | 'indigo': Colors.indigo,
45 | 'brown': Colors.brown,
46 | 'grey': Colors.grey,
47 | };
48 |
49 | final Color initialColour;
50 |
51 | const ColourPicker({
52 | required this.initialColour,
53 | super.key,
54 | });
55 |
56 | @override
57 | State createState() => _ColourPickerState();
58 | }
59 |
60 | class _ColourPickerState extends State {
61 | late Color _colour;
62 |
63 | @override
64 | void initState() {
65 | super.initState();
66 |
67 | _colour = widget.initialColour;
68 | }
69 |
70 | @override
71 | void didChangeDependencies() {
72 | super.didChangeDependencies();
73 | }
74 |
75 | @override
76 | Widget build(BuildContext context) {
77 | final ColorScheme colorScheme = Theme.of(context).colorScheme;
78 |
79 | // The header should use the primary color in light themes and surface color in dark
80 | final bool isDark = colorScheme.brightness == Brightness.dark;
81 | final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
82 | final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
83 |
84 | return Dialog(
85 | child: SingleChildScrollView(
86 | child: Column(
87 | mainAxisSize: MainAxisSize.min,
88 | crossAxisAlignment: CrossAxisAlignment.stretch,
89 | children: [
90 | Container(
91 | padding: const EdgeInsets.fromLTRB(16, 64, 16, 16),
92 | decoration: BoxDecoration(
93 | borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
94 | color: primarySurfaceColor,
95 | ),
96 | child: Text(
97 | translate('dialog.picker.colour.title'),
98 | style: Theme.of(context).textTheme.titleLarge!.copyWith(color: onPrimarySurfaceColor),
99 | ),
100 | ),
101 | GridView.builder(
102 | gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
103 | maxCrossAxisExtent: 64,
104 | childAspectRatio: 1,
105 | crossAxisSpacing: 8,
106 | mainAxisSpacing: 8,
107 | ),
108 | padding: const EdgeInsets.all(16),
109 | shrinkWrap: true,
110 | itemCount: colours.length,
111 | itemBuilder: (context, index) {
112 | var colour = colours.values.elementAt(index);
113 |
114 | return InkWell(
115 | borderRadius: BorderRadius.circular(16),
116 | onTap: () {
117 | setState(() {
118 | _colour = colour;
119 | Navigator.of(context).pop(colour);
120 | });
121 | },
122 | child: Ink(
123 | decoration: BoxDecoration(
124 | borderRadius: BorderRadius.circular(16),
125 | color: colour,
126 | border: Border.all(
127 | color: _colour == colour ? Theme.of(context).colorScheme.primary : Colors.transparent,
128 | width: 2,
129 | ),
130 | boxShadow: [
131 | BoxShadow(color: colour.withOpacity(0.5), blurRadius: 2, offset: const Offset(0, 2)),
132 | BoxShadow(color: colour.withOpacity(0.25), blurRadius: 5, offset: const Offset(0, 5)),
133 | ],
134 | ),
135 | ),
136 | );
137 | },
138 | ),
139 | ButtonBar(
140 | children: [
141 | TextButton(
142 | onPressed: () {
143 | Navigator.of(context).pop();
144 | },
145 | child: Text(translate('general.cancel')),
146 | ),
147 | ],
148 | ),
149 | ],
150 | ),
151 | ),
152 | );
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/noterly/lib/pages/main_page/archived_notifications_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:firebase_analytics/firebase_analytics.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_translate/flutter_translate.dart';
4 | import 'package:noterly/extensions/date_time_extensions.dart';
5 | import 'package:noterly/managers/app_manager.dart';
6 | import 'package:noterly/models/navigation_screen.dart';
7 | import 'package:noterly/models/notification_item.dart';
8 |
9 | import '../edit_notification_page.dart';
10 |
11 | class ArchivedNotificationsPage extends NavigationScreen {
12 | final ScrollController scrollController;
13 | final List items;
14 |
15 | const ArchivedNotificationsPage({
16 | super.key,
17 | required this.items,
18 | required this.scrollController,
19 | });
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | if (items.isEmpty) {
24 | return Center(
25 | child: Text(translate('page.archived_notifications.empty')),
26 | );
27 | }
28 |
29 | items.sort((a, b) => b.archivedDateTime!.compareTo(a.archivedDateTime!));
30 |
31 | // TODO: rework to allow for card design
32 | return ListView.builder(
33 | controller: scrollController,
34 | itemCount: items.length + (items.length > 1 ? 1 : 0),
35 | padding: const EdgeInsets.only(bottom: 72), // Allow space for the FAB.
36 | itemBuilder: (context, index) {
37 | if (index == items.length) {
38 | // button to delete all archived notifications
39 | return Padding(
40 | padding: const EdgeInsets.all(16),
41 | child: ElevatedButton(
42 | onPressed: () {
43 | AppManager.instance.deleteAllArchivedItems();
44 |
45 | ScaffoldMessenger.of(context).clearSnackBars(); // Clear any existing snackbars
46 | ScaffoldMessenger.of(context).showSnackBar(
47 | SnackBar(
48 | content: Text(translate('snackbar.all_archived_notifications_deleted')),
49 | action: SnackBarAction(
50 | label: translate('general.undo'),
51 | onPressed: () {
52 | AppManager.instance.restoreLastDeletedItems();
53 | FirebaseAnalytics.instance.logEvent(name: 'restore_all_deleted_items');
54 | },
55 | ),
56 | ),
57 | );
58 |
59 | FirebaseAnalytics.instance.logEvent(name: 'delete_all_archived_items');
60 | },
61 | child: Text(translate('page.archived_notifications.button.delete_all')),
62 | ),
63 | );
64 | }
65 |
66 | final item = items[index];
67 |
68 | return Dismissible(
69 | key: ValueKey(item.id),
70 | background: _getDismissibleBackground(context),
71 | secondaryBackground: _getDismissibleBackground(context, isSecondary: true),
72 | onDismissed: (direction) {
73 | AppManager.instance.deleteItem(item.id);
74 | ScaffoldMessenger.of(context).clearSnackBars(); // Clear any existing snackbars to prevent buildup
75 | ScaffoldMessenger.of(context).showSnackBar(
76 | SnackBar(
77 | content: Text(translate('snackbar.notification_deleted', args: {'title': item.title})),
78 | action: SnackBarAction(
79 | label: translate('general.undo'),
80 | onPressed: () {
81 | AppManager.instance.restoreLastDeletedItems();
82 | FirebaseAnalytics.instance.logEvent(name: 'restore_deleted_item');
83 | },
84 | ),
85 | ),
86 | );
87 |
88 | FirebaseAnalytics.instance.logEvent(
89 | name: 'delete_item',
90 | parameters: {'from': 'archived_notifications_page'},
91 | );
92 | },
93 | child: ListTile(
94 | title: Text(item.title),
95 | subtitle: _getSubtitle(context, item),
96 | minVerticalPadding: 12,
97 | leading: SizedBox(
98 | width: 32,
99 | child: Align(
100 | alignment: Alignment.center,
101 | child: CircleAvatar(
102 | radius: 8,
103 | backgroundColor: item.colour,
104 | ),
105 | ),
106 | ),
107 | onTap: () => _onItemTap(context, item),
108 | ),
109 | );
110 | },
111 | );
112 | }
113 |
114 | Widget _getDismissibleBackground(BuildContext context, {bool isSecondary = false}) => Container(
115 | color: Theme.of(context).colorScheme.primary,
116 | child: Align(
117 | alignment: isSecondary ? Alignment.centerRight : Alignment.centerLeft,
118 | child: Padding(
119 | padding: const EdgeInsets.symmetric(horizontal: 16),
120 | child: Icon(
121 | Icons.delete,
122 | color: Theme.of(context).colorScheme.onPrimary,
123 | ),
124 | ),
125 | ),
126 | );
127 |
128 | Widget _getSubtitle(BuildContext context, NotificationItem item) => Column(
129 | crossAxisAlignment: CrossAxisAlignment.start,
130 | children: [
131 | if (item.body.isNotEmpty) Text(item.body),
132 | Row(
133 | children: [
134 | const Icon(Icons.history, size: 16),
135 | const SizedBox(width: 6),
136 | Flexible(
137 | child: Text(
138 | translate('page.archived_notifications.item.archived', args: {'date_time': item.archivedDateTime!.toRelativeDateTimeString()}),
139 | style: Theme.of(context).textTheme.labelLarge,
140 | ),
141 | ),
142 | ],
143 | ),
144 | ],
145 | );
146 |
147 | void _onItemTap(BuildContext context, NotificationItem item) => Navigator.of(context).push(
148 | MaterialPageRoute(
149 | builder: (context) => EditNotificationPage(
150 | item: item,
151 | ),
152 | ),
153 | );
154 | }
155 |
--------------------------------------------------------------------------------
/noterly/lib/widgets/duration_picker.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_translate/flutter_translate.dart';
3 | import 'package:noterly/extensions/duration_extensions.dart';
4 |
5 | Future showDurationPicker({
6 | required BuildContext context,
7 | required Duration initialDuration,
8 | }) async {
9 | var dialog = DurationPicker(
10 | initialDuration: initialDuration,
11 | );
12 |
13 | return showDialog(
14 | context: context,
15 | builder: (context) => dialog,
16 | );
17 | }
18 |
19 | class DurationPicker extends StatefulWidget {
20 | final Duration initialDuration;
21 |
22 | const DurationPicker({
23 | required this.initialDuration,
24 | super.key,
25 | });
26 |
27 | @override
28 | State createState() => _DurationPickerState();
29 | }
30 |
31 | class _DurationPickerState extends State {
32 | late Duration _duration;
33 | int _value = 1;
34 |
35 | late TextEditingController _intervalController;
36 |
37 | @override
38 | void initState() {
39 | super.initState();
40 |
41 | _value = widget.initialDuration.inHours > 0 ? widget.initialDuration.inHours : widget.initialDuration.inMinutes;
42 | _intervalController = TextEditingController(text: _value.toString());
43 |
44 | _duration = widget.initialDuration;
45 | }
46 |
47 | @override
48 | void dispose() {
49 | _intervalController.dispose();
50 |
51 | super.dispose();
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | final ColorScheme colorScheme = Theme.of(context).colorScheme;
57 |
58 | // The header should use the primary color in light themes and surface color in dark
59 | final bool isDark = colorScheme.brightness == Brightness.dark;
60 | final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
61 | final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
62 |
63 | return Dialog(
64 | child: Column(
65 | mainAxisSize: MainAxisSize.min,
66 | crossAxisAlignment: CrossAxisAlignment.stretch,
67 | children: [
68 | Container(
69 | padding: const EdgeInsets.fromLTRB(16, 64, 16, 16),
70 | decoration: BoxDecoration(
71 | borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
72 | color: primarySurfaceColor,
73 | ),
74 | child: Column(
75 | crossAxisAlignment: CrossAxisAlignment.start,
76 | children: [
77 | Text(
78 | translate('dialog.picker.duration.title'),
79 | style: Theme.of(context).textTheme.titleLarge!.copyWith(color: onPrimarySurfaceColor),
80 | ),
81 | Text(
82 | translate('dialog.picker.duration.subtitle', args: {'duration': _duration.toRelativeDurationString()}),
83 | style: Theme.of(context).textTheme.titleMedium!.copyWith(color: onPrimarySurfaceColor),
84 | ),
85 | ],
86 | ),
87 | ),
88 | _getMainSection(),
89 | ButtonBar(
90 | children: [
91 | TextButton(
92 | onPressed: () {
93 | Navigator.of(context).pop();
94 | },
95 | child: Text(translate('general.cancel')),
96 | ),
97 | TextButton(
98 | onPressed: () {
99 | Navigator.of(context).pop(_duration);
100 | },
101 | child: Text(translate('general.ok')),
102 | ),
103 | ],
104 | ),
105 | ],
106 | ),
107 | );
108 | }
109 |
110 | Widget _getMainSection() => Padding(
111 | padding: const EdgeInsets.all(16.0),
112 | child: Row(
113 | children: [
114 | Flexible(
115 | flex: 1,
116 | child: TextField(
117 | decoration: InputDecoration(
118 | border: const OutlineInputBorder(),
119 | labelText: translate('dialog.picker.duration.field.number.label'),
120 | ),
121 | controller: _intervalController,
122 | onChanged: (value) {
123 | if (value.isEmpty) return;
124 | var number = int.tryParse(value);
125 | if (number == null) return;
126 |
127 | setState(() {
128 | _value = number;
129 | _duration = _duration.inHours > 0 ? Duration(hours: number) : Duration(minutes: number);
130 | });
131 | },
132 | onSubmitted: (value) {
133 | var number = int.tryParse(value) ?? 1;
134 |
135 | setState(() {
136 | _value = number;
137 | _duration = _duration.inHours > 0 ? Duration(hours: number) : Duration(minutes: number);
138 | _intervalController.text = number.toString();
139 | });
140 | },
141 | onEditingComplete: () {},
142 | ),
143 | ),
144 | const SizedBox(width: 16),
145 | Flexible(
146 | flex: 2,
147 | child: DropdownButtonFormField(
148 | decoration: InputDecoration(
149 | border: const OutlineInputBorder(),
150 | labelText: translate('dialog.picker.duration.field.period.label'),
151 | ),
152 | items: [
153 | DropdownMenuItem(value: 'hour', child: Text(translate('time.hour${_value == 1 ? '' : 's'}'))),
154 | DropdownMenuItem(value: 'minute', child: Text(translate('time.minute${_value == 1 ? '' : 's'}'))),
155 | ],
156 | value: _duration.inHours > 0 ? 'hour' : 'minute',
157 | onChanged: (value) {
158 | setState(() {
159 | _duration = value == 'hour' ? Duration(hours: _value) : Duration(minutes: _value);
160 | });
161 | },
162 | ),
163 | ),
164 | ],
165 | ),
166 | );
167 | }
168 |
--------------------------------------------------------------------------------
/website/css/app.css:
--------------------------------------------------------------------------------
1 | @import"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;700&display=swap";*{box-sizing:border-box}html,body{overflow-x:hidden}html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}::-moz-selection{color:#fff;background:#aa00f1}::selection{color:#fff;background:#aa00f1}*{font-family:"DM Sans",sans-serif}p{line-height:1.75rem}.card{background:linear-gradient(135deg, #1A131F, #0F041B);padding:4rem 2rem;min-height:20rem;border-radius:1rem;z-index:10;box-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12)}@media(max-width: 48em){.card{padding:2rem 1rem}}.card .content{display:flex;flex-direction:column;align-items:start;justify-content:center;gap:1rem}.card .content iconify-icon{color:#e4b5ff;font-size:3rem}.card .content h2{color:#e4b5ff;text-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12);font-size:2rem}.card .content p{color:#e2b3ff;font-size:1.2rem}section>.content,footer>.content{max-width:1200px;width:100%;padding:0 2rem}@media(max-width: 48em){section>.content,footer>.content{padding:0 1rem}}section,footer{position:relative;display:flex;align-items:center;flex-direction:column}.adaptive-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(300px, 1fr));gap:2rem;margin:2rem 0}@media(max-width: 24em){.adaptive-grid{grid-template-columns:repeat(auto-fit, minmax(100%, 1fr))}}@media(max-width: 48em){.adaptive-grid{gap:1rem}}#hero{min-height:100vh;z-index:20}#hero .background{width:100vw;filter:drop-shadow(0px 5.1px 4.3px rgba(0, 0, 0, 0.042)) drop-shadow(0px 14.1px 11.8px rgba(0, 0, 0, 0.06)) drop-shadow(0px 34.1px 28.3px rgba(0, 0, 0, 0.078)) drop-shadow(0px 113px 94px rgba(0, 0, 0, 0.12))}#hero .background>*{width:100%;background:linear-gradient(135deg, #1A131F, #0F041B);background-attachment:fixed;transform:translateZ(0)}#hero .background .background-fill{min-height:100vh}#hero .background .background-waves{height:20rem;-webkit-clip-path:url(#waves-top-clip);clip-path:url(#waves-top-clip)}@media(max-width: 64em){#hero .background .background-waves{-webkit-clip-path:url(#waves-top-clip-mobile);clip-path:url(#waves-top-clip-mobile)}}#hero .content{z-index:100;position:absolute;top:0;width:100%;height:calc(100% - 20rem);display:flex;justify-content:center;gap:1rem;flex-direction:column}@media(max-width: 64em){#hero .content{align-items:center}#hero .content h1,#hero .content h2{text-align:center}}#hero .content h1{color:#e4b5ff;font-size:clamp(2.5rem,10vw,4rem);text-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12)}#hero .content h2{color:#e2b3ff;font-size:clamp(1.5rem,5vw,1.75rem);font-weight:400}#hero .content .logo{position:relative;width:clamp(4rem,20vw,8rem);height:clamp(4rem,20vw,8rem);margin-bottom:1rem;border-radius:clamp(1rem,5vw,2rem);box-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12)}#hero .content .logo .foreground,#hero .content .logo .background{width:100%;position:absolute;border-radius:clamp(1rem,5vw,2rem)}#hero .content .buttons{margin-top:1rem}#hero .content .screenshot{position:absolute;right:2rem}@media(max-width: 64em){#hero .content .screenshot{position:relative;right:0;margin-top:3rem}}#hero .content .screenshot img{height:60vh;max-width:100vw;-o-object-fit:contain;object-fit:contain;border-radius:1rem;box-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12)}@media(max-width: 64em){#hero .content .screenshot img{height:40vh}}@media(max-width: 12em){#hero .content .screenshot img{height:auto;width:70vw}}section#about{background:linear-gradient(135deg, #BB00FD, #5B00B6);margin-top:-20rem;z-index:10;padding-top:35rem;padding-bottom:35rem;width:100%}section#about .content .card-grid{width:100%}footer{position:relative;margin-top:-20rem;z-index:20}footer #footer-background{z-index:10;width:100vw;height:35vh;filter:drop-shadow(0px 5.1px 4.3px rgba(0, 0, 0, 0.042)) drop-shadow(0px 14.1px 11.8px rgba(0, 0, 0, 0.06)) drop-shadow(0px 34.1px 28.3px rgba(0, 0, 0, 0.078)) drop-shadow(0px 113px 94px rgba(0, 0, 0, 0.12))}footer .background{width:100vw;filter:drop-shadow(0px 5.1px 4.3px rgba(0, 0, 0, 0.042)) drop-shadow(0px 14.1px 11.8px rgba(0, 0, 0, 0.06)) drop-shadow(0px 34.1px 28.3px rgba(0, 0, 0, 0.078)) drop-shadow(0px 113px 94px rgba(0, 0, 0, 0.12))}footer .background>*{width:100%;background:linear-gradient(135deg, #1A131F, #0F041B);background-attachment:fixed;transform:translateZ(0)}footer .background .background-waves{height:20rem;-webkit-clip-path:url(#waves-bottom-clip);clip-path:url(#waves-bottom-clip)}@media(max-width: 64em){footer .background .background-waves{-webkit-clip-path:url(#waves-bottom-clip-mobile);clip-path:url(#waves-bottom-clip-mobile)}}footer .background .background-fill{height:25rem}footer .content{z-index:100;position:absolute;top:15rem;width:100vw}footer .content h2{color:#e4b5ff;text-shadow:0px 5.1px 4.3px rgba(0,0,0,.042),0px 14.1px 11.8px rgba(0,0,0,.06),0px 34.1px 28.3px rgba(0,0,0,.078),0px 113px 94px rgba(0,0,0,.12)}footer .content p{color:#e2b3ff;font-weight:400}footer .content .footer-grid{width:100%;display:flex;justify-content:space-between;margin-top:1rem;gap:1rem;flex-direction:row}@media(max-width: 48em){footer .content .footer-grid{flex-direction:column}}footer .content .footer-grid .column{display:flex;flex-direction:column}footer .content .footer-grid .column hr{max-width:100px;width:100%;border-top:1px solid rgba(226,179,255,.25);border-right:none;border-bottom:none;border-left:none}footer .content .footer-grid .column.text{gap:.5rem}footer .content .footer-grid .column.links{gap:1rem}footer .content .footer-grid .column.links a{display:inline-flex;align-items:center;gap:.5rem;color:#e2b3ff;text-decoration:none}footer .content .footer-grid .column.links a:hover{text-decoration:underline}
--------------------------------------------------------------------------------
/noterly/lib/widgets/repetition_picker.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_translate/flutter_translate.dart';
3 | import 'package:noterly/models/repetition_data.dart';
4 |
5 | Future showRepetitionPicker({
6 | required BuildContext context,
7 | required RepetitionData initialRepetitionData,
8 | }) async {
9 | var dialog = RepetitionPicker(
10 | initialRepetitionData: initialRepetitionData,
11 | );
12 |
13 | return showDialog(
14 | context: context,
15 | builder: (context) => dialog,
16 | );
17 | }
18 |
19 | class RepetitionPicker extends StatefulWidget {
20 | final RepetitionData initialRepetitionData;
21 |
22 | const RepetitionPicker({
23 | required this.initialRepetitionData,
24 | super.key,
25 | });
26 |
27 | @override
28 | State createState() => _RepetitionPickerState();
29 | }
30 |
31 | class _RepetitionPickerState extends State {
32 | late RepetitionData _repetitionData;
33 |
34 | late TextEditingController _intervalController;
35 |
36 | @override
37 | void initState() {
38 | super.initState();
39 |
40 | _intervalController = TextEditingController(text: widget.initialRepetitionData.number.toString());
41 |
42 | _repetitionData = widget.initialRepetitionData;
43 | }
44 |
45 | @override
46 | void dispose() {
47 | _intervalController.dispose();
48 |
49 | super.dispose();
50 | }
51 |
52 | @override
53 | Widget build(BuildContext context) {
54 | final ColorScheme colorScheme = Theme.of(context).colorScheme;
55 |
56 | // The header should use the primary color in light themes and surface color in dark
57 | final bool isDark = colorScheme.brightness == Brightness.dark;
58 | final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
59 | final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
60 |
61 | return Dialog(
62 | child: Column(
63 | mainAxisSize: MainAxisSize.min,
64 | crossAxisAlignment: CrossAxisAlignment.stretch,
65 | children: [
66 | Container(
67 | padding: const EdgeInsets.fromLTRB(16, 64, 16, 16),
68 | decoration: BoxDecoration(
69 | borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
70 | color: primarySurfaceColor,
71 | ),
72 | child: Column(
73 | crossAxisAlignment: CrossAxisAlignment.start,
74 | children: [
75 | Text(
76 | translate('dialog.picker.repetition.title'),
77 | style: Theme.of(context).textTheme.titleLarge!.copyWith(color: onPrimarySurfaceColor),
78 | ),
79 | Text(
80 | translate('dialog.picker.repetition.subtitle', args: {'duration': _repetitionData.toReadableString()}),
81 | style: Theme.of(context).textTheme.titleMedium!.copyWith(color: onPrimarySurfaceColor),
82 | ),
83 | ],
84 | ),
85 | ),
86 | _getMainSection(),
87 | ButtonBar(
88 | children: [
89 | TextButton(
90 | onPressed: () {
91 | Navigator.of(context).pop();
92 | },
93 | child: Text(translate('general.cancel')),
94 | ),
95 | TextButton(
96 | onPressed: () {
97 | Navigator.of(context).pop(_repetitionData);
98 | },
99 | child: Text(translate('general.ok')),
100 | ),
101 | ],
102 | ),
103 | ],
104 | ),
105 | );
106 | }
107 |
108 | Widget _getMainSection() => Padding(
109 | padding: const EdgeInsets.all(16.0),
110 | child: Row(
111 | children: [
112 | Flexible(
113 | flex: 1,
114 | child: TextField(
115 | decoration: InputDecoration(
116 | border: const OutlineInputBorder(),
117 | labelText: translate('dialog.picker.repetition.field.number.label'),
118 | ),
119 | controller: _intervalController,
120 | onChanged: (value) {
121 | if (value.isEmpty) return;
122 |
123 | var number = int.tryParse(value);
124 | if (number == null) return;
125 | if (number < 1) number = 1;
126 |
127 | setState(() {
128 | _repetitionData.number = number!;
129 | });
130 | },
131 | onSubmitted: (value) {
132 | var number = int.tryParse(value) ?? 1;
133 | if (number < 1) number = 1;
134 |
135 | setState(() {
136 | _repetitionData.number = number;
137 | _intervalController.text = number.toString();
138 | });
139 | },
140 | onEditingComplete: () {},
141 | ),
142 | ),
143 | const SizedBox(width: 16),
144 | Flexible(
145 | flex: 2,
146 | child: DropdownButtonFormField(
147 | decoration: InputDecoration(
148 | border: const OutlineInputBorder(),
149 | labelText: translate('dialog.picker.repetition.field.period.label'),
150 | ),
151 | items: [
152 | DropdownMenuItem(value: Repetition.hourly, child: Text(translate('time.hour${_repetitionData.number == 1 ? '' : 's'}'))),
153 | DropdownMenuItem(value: Repetition.daily, child: Text(translate('time.day${_repetitionData.number == 1 ? '' : 's'}'))),
154 | DropdownMenuItem(value: Repetition.weekly, child: Text(translate('time.week${_repetitionData.number == 1 ? '' : 's'}'))),
155 | DropdownMenuItem(value: Repetition.monthly, child: Text(translate('time.month${_repetitionData.number == 1 ? '' : 's'}'))),
156 | DropdownMenuItem(value: Repetition.yearly, child: Text(translate('time.year${_repetitionData.number == 1 ? '' : 's'}'))),
157 | ],
158 | value: _repetitionData.type,
159 | onChanged: (value) {
160 | setState(() {
161 | _repetitionData.type = value!;
162 | });
163 | },
164 | ),
165 | ),
166 | ],
167 | ),
168 | );
169 | }
170 |
--------------------------------------------------------------------------------
/website/img/google_play_badge.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/noterly/lib/managers/app_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:logger/logger.dart';
3 | import 'package:noterly/managers/file_manager.dart';
4 | import 'package:noterly/managers/lifecycle_event_handler.dart';
5 | import 'package:noterly/managers/notification_manager.dart';
6 | import 'package:noterly/models/app_data.dart';
7 | import 'package:noterly/models/notification_item.dart';
8 |
9 | import 'log.dart';
10 |
11 | class AppManager {
12 | static final AppManager _instance = AppManager._internal();
13 |
14 | static AppManager get instance => _instance;
15 |
16 | AppManager._internal() {
17 | WidgetsBinding.instance.addObserver(
18 | LifecycleEventHandler(
19 | resumeCallback: () async {
20 | // If we're resuming, we need to reload the data from file in case an item has been deleted from a notification
21 | // action. In this case, a separate instance of the app will have been launched to handle the action, and the
22 | // data will have been saved to file. We need to reload the data from file to ensure the UI is up to date.
23 | Log.logger.d('Resuming app, reloading data from file...');
24 | await fullUpdate();
25 |
26 | // We also need to update the notifications, to ensure any notifications that need to be displayed are displayed.
27 | Log.logger.d('Updating notifications...');
28 | await NotificationManager.instance.updateAllNotifications();
29 | },
30 | suspendingCallback: () async => Log.logger.d('App suspended.'),
31 | ),
32 | );
33 |
34 | _load();
35 | }
36 |
37 | final Logger _logger = Logger(
38 | printer: PrettyPrinter(
39 | methodCount: 2,
40 | errorMethodCount: 8,
41 | lineLength: 120,
42 | colors: true,
43 | printEmojis: true,
44 | printTime: true,
45 | ),
46 | );
47 |
48 | Logger get logger => _logger;
49 |
50 | AppData data = AppData.defaults();
51 |
52 | final notifier = ValueNotifier>([]);
53 |
54 | List deletedItems = [];
55 |
56 | var isInitialised = false;
57 | Future? _loadingFuture;
58 |
59 | Future ensureInitialised() async {
60 | if (isInitialised) {
61 | return;
62 | }
63 |
64 | return _loadingFuture;
65 | }
66 |
67 | Future _load() async {
68 | _loadingFuture = FileManager.load();
69 | var newData = await _loadingFuture;
70 |
71 | _loadingFuture = null;
72 |
73 | if (newData == null) {
74 | Log.logger.d('No (valid) previous save found.');
75 | notifier.value = [];
76 | return;
77 | }
78 |
79 | if (newData != null) data = newData;
80 | notifier.value = data.notificationItems;
81 |
82 | Log.logger.d('Loaded data from file.');
83 |
84 | isInitialised = true;
85 | }
86 |
87 | Future _save() async {
88 | Log.logger.d('Saving data to file...');
89 | data.notificationItems = notifier.value;
90 |
91 | await FileManager.save(data);
92 | }
93 |
94 | // #region List management
95 | NotificationItem itemAt(int i) => notifier.value[i];
96 |
97 | NotificationItem? getItem(String id) {
98 | var found = notifier.value.where((element) => element.id == id);
99 | return found.isEmpty ? null : found.first;
100 | }
101 |
102 | Future addItem(NotificationItem item, {bool deferNotificationManagerCall = false}) async {
103 | notifier.value.add(item);
104 | await _save();
105 | _updateNotifier();
106 |
107 | if (!deferNotificationManagerCall) {
108 | NotificationManager.instance.showOrUpdateNotification(item);
109 | }
110 | }
111 |
112 | Future editItem(NotificationItem item, {bool deferNotificationManagerCall = false}) async {
113 | var found = notifier.value.where((element) => element.id == item.id);
114 | if (found.isEmpty) {
115 | return;
116 | }
117 |
118 | var index = notifier.value.indexOf(found.first);
119 | notifier.value[index] = item;
120 | await _save();
121 | _updateNotifier();
122 |
123 | if (!deferNotificationManagerCall) {
124 | NotificationManager.instance.showOrUpdateNotification(item);
125 | }
126 | }
127 |
128 | Future deleteItem(String id, {bool deferNotificationManagerCall = false}) async {
129 | var found = notifier.value.where((element) => element.id == id);
130 | if (found.isEmpty) {
131 | return;
132 | }
133 |
134 | deletedItems = [found.first];
135 | notifier.value.remove(found.first);
136 |
137 | await _save();
138 | _updateNotifier();
139 |
140 | if (!deferNotificationManagerCall) {
141 | await NotificationManager.instance.cancelNotification(id);
142 | }
143 | }
144 |
145 | Future deleteAllArchivedItems({bool deferNotificationManagerCall = false}) async {
146 | var archivedItems = notifier.value.where((element) => element.archived).toList();
147 | if (archivedItems.isEmpty) {
148 | return;
149 | }
150 |
151 | deletedItems = archivedItems;
152 |
153 | for (var element in archivedItems) {
154 | notifier.value.remove(element);
155 | if (!deferNotificationManagerCall) {
156 | await NotificationManager.instance.cancelNotification(element.id);
157 | }
158 | }
159 |
160 | await _save();
161 | _updateNotifier();
162 | }
163 |
164 | Future archiveItem(String id, {bool deferNotificationManagerCall = false}) async {
165 | var found = notifier.value.where((element) => element.id == id);
166 | if (found.isEmpty) {
167 | return;
168 | }
169 |
170 | var index = notifier.value.indexOf(found.first);
171 | notifier.value[index].archived = true;
172 | notifier.value[index].archivedDateTime = DateTime.now();
173 | await _save();
174 | _updateNotifier();
175 |
176 | if (!deferNotificationManagerCall) {
177 | await NotificationManager.instance.cancelNotification(id);
178 | }
179 | }
180 |
181 | Future restoreArchivedItem(String id, {bool deferNotificationManagerCall = false}) async {
182 | var found = notifier.value.where((element) => element.id == id);
183 | if (found.isEmpty) {
184 | return;
185 | }
186 |
187 | var index = notifier.value.indexOf(found.first);
188 | notifier.value[index].archived = false;
189 | notifier.value[index].archivedDateTime = null;
190 | await _save();
191 | _updateNotifier();
192 |
193 | if (!deferNotificationManagerCall) {
194 | NotificationManager.instance.showOrUpdateNotification(notifier.value[index]);
195 | }
196 | }
197 |
198 | Future restoreLastDeletedItems({bool deferNotificationManagerCall = false}) async {
199 | if (deletedItems.isEmpty) {
200 | return;
201 | }
202 |
203 | for (var item in deletedItems) {
204 | await addItem(item!, deferNotificationManagerCall: deferNotificationManagerCall);
205 | }
206 |
207 | deletedItems = [];
208 | }
209 |
210 | // #endregion
211 |
212 | Future saveSettings() async {
213 | Log.logger.d('Saving data for settings...');
214 | await _save();
215 | _updateNotifier();
216 | Log.logger.d('Data saved.');
217 | }
218 |
219 | Future fullUpdate() async {
220 | Log.logger.d('Full update requested, reloading data from file...');
221 | await _load();
222 | _updateNotifier();
223 | Log.logger.d('Full update completed.');
224 | printItems();
225 | }
226 |
227 | void _updateNotifier() {
228 | notifier.value = List.from(notifier.value); // Update value notifier
229 | }
230 |
231 | void printItems() => Log.logger.d(notifier.value);
232 | }
233 |
--------------------------------------------------------------------------------
/scripts/i18n/out/en_US.json:
--------------------------------------------------------------------------------
1 | {"main.action.new": "New note", "main.action.create": "Create", "main.action.save": "Save", "main.action.reactivate": "Reactivate", "page.active_notifications.label": "Active", "page.active_notifications.header.immediate": "Shown immediately", "page.active_notifications.header.scheduled": "Scheduled", "page.active_notifications.header.repeating": "Repeating", "page.active_notifications.empty": "No active notifications.", "page.active_notifications.item.repeats": "Repeats {duration}", "page.active_notifications.item.snoozed": "Snoozed {date_time}", "page.archived_notifications.label": "Archive", "page.archived_notifications.empty": "Archived notifications will appear here.", "page.archived_notifications.button.delete_all": "Delete all archived notifications", "page.archived_notifications.item.archived": "Archived {date_time}", "snackbar.notification_deleted": "Notification \"{title}\" deleted.", "snackbar.notification_archived": "Notification \"{title}\" archived.", "snackbar.all_archived_notifications_deleted": "All archived notifications deleted.", "toast.notification_snoozed": "Notification snoozed for {duration}", "page.settings.title": "Settings", "page.settings.header.notifications": "Notifications", "page.settings.header.system": "System", "page.settings.header.about": "About", "page.settings.header.debug": "Debug options", "page.settings.header.debug.disclaimer": "These options are not supported and may cause issues. Use at your own risk.", "page.settings.notifications.snooze_duration": "Snooze duration", "page.settings.system.notification_settings": "Notification settings", "page.settings.about.version.title": "Version", "page.settings.about.copyright.title": "Copyright & credits", "page.settings.about.copyright.text": "2023 Tom Chapman, TDS Studios.", "page.settings.about.tutorial.title": "Tutorial", "page.settings.about.licenses.title": "Licenses", "page.settings.about.licenses.page.legalese": "Copyright \u00a9 2023 Tom Chapman, TDS Studios.", "page.settings.about.privacy_policy.title": "Privacy policy", "page.settings.about.feedback.title": "Send feedback", "page.settings.about.translate.title": "Help translate Noterly", "page.settings.about.donation.title": "Buy me a coffee", "page.create_notification.title": "Create Notification", "page.create_notification.header.details": "Notification details", "page.create_notification.header.timing": "Timing", "page.create_notification.timing.schedule.title": "Schedule", "page.create_notification.timing.schedule.subtitle": "Send", "page.create_notification.timing.repeat.title": "Repeating", "page.create_notification.timing.repeat.subtitle": "Repeat", "page.create_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.create_notification.details.field.title.label": "Title", "page.create_notification.details.field.title.error": "Please enter a title", "page.create_notification.details.field.body.label": "Body", "page.create_notification.details.field.colour.label": "Color", "page.edit_notification.title": "Edit Notification", "page.edit_notification.header.details": "Notification details", "page.edit_notification.header.timing": "Timing", "page.edit_notification.timing.schedule.title": "Schedule", "page.edit_notification.timing.schedule.subtitle": "Send", "page.edit_notification.timing.repeat.title": "Repeating", "page.edit_notification.timing.repeat.subtitle": "Repeat", "page.edit_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.edit_notification.details.field.title.label": "Title", "page.edit_notification.details.field.title.error": "Please enter a title", "page.edit_notification.details.field.body.label": "Body", "page.edit_notification.details.field.colour.label": "Color", "item.title": "Title", "item.body": "Body", "item.colour": "Color", "notification.action.mark_as_done": "Mark as done", "notification.action.snooze": "Snooze", "notification.action.snooze_for": "Snooze for {duration}", "dialog.picker.date_time.title": "Select date and time", "dialog.picker.date_time.header.date": "Date", "dialog.picker.date_time.header.time": "Time", "dialog.picker.date_time.button.select_date": "Select date", "dialog.picker.date_time.button.select_time": "Select time", "dialog.picker.repetition.title": "Select repeat duration", "dialog.picker.repetition.subtitle": "repeats {duration}", "dialog.picker.repetition.field.number.label": "Number", "dialog.picker.repetition.field.period.label": "Period", "dialog.picker.duration.title": "Select duration", "dialog.picker.duration.subtitle": "{duration}", "dialog.picker.duration.field.number.label": "Number", "dialog.picker.duration.field.period.label": "Period", "dialog.picker.colour.title": "Select color", "dialog.easter_egg.title": "Easter egg!", "dialog.easter_egg.text": "Congratulations, you've found the easter egg! You can now enable debug options. Please note that these options are not supported and may cause issues.", "dialog_easter_egg.action.show_debug_options": "Show debug options", "dialog.about.translations.title": "Translations", "dialog.about.action.show_licenses": "Licenses", "tutorial.page.0.title": "Welcome to Noterly", "tutorial.page.0.subtitle": "Simple notification reminders", "tutorial.page.0.content": "Here's some basic information to get you started.", "tutorial.page.1.title": "Reminders", "tutorial.page.1.content": "Noterly is designed for quick, simple reminders. You may find a to-do list or calendar app more suitable for more complex tasks.", "tutorial.page.2.title": "Create a notification", "tutorial.page.2.content": "Tap the floating button to create a new notification.", "tutorial.page.2.confirmation.0": "That's right!", "tutorial.page.2.confirmation.1": "Well done!", "tutorial.page.2.confirmation.2": "You've got it!", "tutorial.page.2.confirmation.3": "Great job!", "tutorial.page.2.confirmation.4": "Great success!", "tutorial.page.3.title": "Manage notifications", "tutorial.page.3.content": "Completed your task? Swipe the notification away. To delete a notification, go to the archive page and swipe it away.", "tutorial.page.3.example": "Swipe to archive or delete", "tutorial.page.4.title": "Snoozing reminders", "tutorial.page.4.content": "You can snooze a reminder by tapping the button in the notification. Customise the snooze duration in settings.", "tutorial.page.5.title": "That's it!", "tutorial.page.5.content": "You're ready to start using Noterly. You can always come back to this tutorial in settings.", "tutorial.updated_experience_text": "You're seeing this because this tutorial experience has been updated.", "tutorial.action.back": "Back", "tutorial.action.next": "Next", "tutorial.action.done": "Done", "tutorial.action.skip": "Skip", "general.ok": "OK", "general.cancel": "Cancel", "general.undo": "Undo", "general.close": "Close", "time.second": "second", "time.seconds": "seconds", "time.seconds.value": "{value} seconds", "time.minute": "minute", "time.minutes": "minutes", "time.minutes.value": "{value} minutes", "time.hour": "hour", "time.hours": "hours", "time.hours.value": "{value} hours", "time.day": "day", "time.days": "days", "time.days.value": "{value} days", "time.week": "week", "time.weeks": "weeks", "time.weeks.value": "{value} weeks", "time.month": "month", "time.months": "months", "time.months.value": "{value} months", "time.year": "year", "time.years": "years", "time.years.value": "{value} years", "time.hourly": "hourly", "time.daily": "daily", "time.weekly": "weekly", "time.monthly": "monthly", "time.yearly": "yearly", "time.repetition.every.value": "every {number} {type}", "time.snooze.until": "until {date_time}", "time.today_and_time": "Today, {time}", "time.yesterday_and_time": "Yesterday, {time}", "time.tomorrow_and_time": "Tomorrow, {time}", "time.date_and_time": "{date}, {time}"}
--------------------------------------------------------------------------------
/noterly/assets/i18n/en_GB.json:
--------------------------------------------------------------------------------
1 | {"main.action.new": "New note", "main.action.create": "Create", "main.action.save": "Save", "main.action.reactivate": "Reactivate", "page.active_notifications.label": "Active", "page.active_notifications.header.immediate": "Shown immediately", "page.active_notifications.header.scheduled": "Scheduled", "page.active_notifications.header.repeating": "Repeating", "page.active_notifications.empty": "No active notifications.", "page.active_notifications.item.repeats": "Repeats {duration}", "page.active_notifications.item.snoozed": "Snoozed {date_time}", "page.archived_notifications.label": "Archive", "page.archived_notifications.empty": "Archived notifications will appear here.", "page.archived_notifications.button.delete_all": "Delete all archived notifications", "page.archived_notifications.item.archived": "Archived {date_time}", "snackbar.notification_deleted": "Notification \"{title}\" deleted.", "snackbar.notification_archived": "Notification \"{title}\" archived.", "snackbar.all_archived_notifications_deleted": "All archived notifications deleted.", "toast.notification_snoozed": "Notification snoozed for {duration}", "page.settings.title": "Settings", "page.settings.header.notifications": "Notifications", "page.settings.header.system": "System", "page.settings.header.about": "About", "page.settings.header.debug": "Debug options", "page.settings.header.debug.disclaimer": "These options are not supported and may cause issues. Use at your own risk.", "page.settings.notifications.snooze_duration": "Snooze duration", "page.settings.system.notification_settings": "Notification settings", "page.settings.about.version.title": "Version", "page.settings.about.copyright.title": "Copyright & credits", "page.settings.about.copyright.text": "2023 Tom Chapman, TDS Studios.", "page.settings.about.tutorial.title": "Tutorial", "page.settings.about.licenses.title": "Licenses", "page.settings.about.licenses.page.legalese": "Copyright \u00a9 2023 Tom Chapman, TDS Studios.", "page.settings.about.privacy_policy.title": "Privacy policy", "page.settings.about.feedback.title": "Send feedback", "page.settings.about.translate.title": "Help translate Noterly", "page.settings.about.donation.title": "Buy me a coffee", "page.create_notification.title": "Create Notification", "page.create_notification.header.details": "Notification details", "page.create_notification.header.timing": "Timing", "page.create_notification.timing.schedule.title": "Schedule", "page.create_notification.timing.schedule.subtitle": "Send", "page.create_notification.timing.repeat.title": "Repeating", "page.create_notification.timing.repeat.subtitle": "Repeat", "page.create_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.create_notification.details.field.title.label": "Title", "page.create_notification.details.field.title.error": "Please enter a title", "page.create_notification.details.field.body.label": "Body", "page.create_notification.details.field.colour.label": "Colour", "page.edit_notification.title": "Edit Notification", "page.edit_notification.header.details": "Notification details", "page.edit_notification.header.timing": "Timing", "page.edit_notification.timing.schedule.title": "Schedule", "page.edit_notification.timing.schedule.subtitle": "Send", "page.edit_notification.timing.repeat.title": "Repeating", "page.edit_notification.timing.repeat.subtitle": "Repeat", "page.edit_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.edit_notification.details.field.title.label": "Title", "page.edit_notification.details.field.title.error": "Please enter a title", "page.edit_notification.details.field.body.label": "Body", "page.edit_notification.details.field.colour.label": "Colour", "item.title": "Title", "item.body": "Body", "item.colour": "Colour", "notification.action.mark_as_done": "Mark as done", "notification.action.snooze": "Snooze", "notification.action.snooze_for": "Snooze for {duration}", "dialog.picker.date_time.title": "Select date and time", "dialog.picker.date_time.header.date": "Date", "dialog.picker.date_time.header.time": "Time", "dialog.picker.date_time.button.select_date": "Select date", "dialog.picker.date_time.button.select_time": "Select time", "dialog.picker.repetition.title": "Select repeat duration", "dialog.picker.repetition.subtitle": "repeats {duration}", "dialog.picker.repetition.field.number.label": "Number", "dialog.picker.repetition.field.period.label": "Period", "dialog.picker.duration.title": "Select duration", "dialog.picker.duration.subtitle": "{duration}", "dialog.picker.duration.field.number.label": "Number", "dialog.picker.duration.field.period.label": "Period", "dialog.picker.colour.title": "Select colour", "dialog.easter_egg.title": "Easter egg!", "dialog.easter_egg.text": "Congratulations, you've found the easter egg! You can now enable debug options. Please note that these options are not supported and may cause issues.", "dialog_easter_egg.action.show_debug_options": "Show debug options", "dialog.about.translations.title": "Translations", "dialog.about.action.show_licenses": "Licenses", "tutorial.page.0.title": "Welcome to Noterly", "tutorial.page.0.subtitle": "Simple notification reminders", "tutorial.page.0.content": "Here's some basic information to get you started.", "tutorial.page.1.title": "Reminders", "tutorial.page.1.content": "Noterly is designed for quick, simple reminders. You may find a to-do list or calendar app more suitable for more complex tasks.", "tutorial.page.2.title": "Create a notification", "tutorial.page.2.content": "Tap the floating button to create a new notification.", "tutorial.page.2.confirmation.0": "That's right!", "tutorial.page.2.confirmation.1": "Well done!", "tutorial.page.2.confirmation.2": "You've got it!", "tutorial.page.2.confirmation.3": "Great job!", "tutorial.page.2.confirmation.4": "Great success!", "tutorial.page.3.title": "Manage notifications", "tutorial.page.3.content": "Completed your task? Swipe the notification away. To delete a notification, go to the archive page and swipe it away.", "tutorial.page.3.example": "Swipe to archive or delete", "tutorial.page.4.title": "Snoozing reminders", "tutorial.page.4.content": "You can snooze a reminder by tapping the button in the notification. Customise the snooze duration in settings.", "tutorial.page.5.title": "That's it!", "tutorial.page.5.content": "You're ready to start using Noterly. You can always come back to this tutorial in settings.", "tutorial.updated_experience_text": "You're seeing this because this tutorial experience has been updated.", "tutorial.action.back": "Back", "tutorial.action.next": "Next", "tutorial.action.done": "Done", "tutorial.action.skip": "Skip", "general.ok": "OK", "general.cancel": "Cancel", "general.undo": "Undo", "general.close": "Close", "time.second": "second", "time.seconds": "seconds", "time.seconds.value": "{value} seconds", "time.minute": "minute", "time.minutes": "minutes", "time.minutes.value": "{value} minutes", "time.hour": "hour", "time.hours": "hours", "time.hours.value": "{value} hours", "time.day": "day", "time.days": "days", "time.days.value": "{value} days", "time.week": "week", "time.weeks": "weeks", "time.weeks.value": "{value} weeks", "time.month": "month", "time.months": "months", "time.months.value": "{value} months", "time.year": "year", "time.years": "years", "time.years.value": "{value} years", "time.hourly": "hourly", "time.daily": "daily", "time.weekly": "weekly", "time.monthly": "monthly", "time.yearly": "yearly", "time.repetition.every.value": "every {number} {type}", "time.snooze.until": "until {date_time}", "time.today_and_time": "Today, {time}", "time.yesterday_and_time": "Yesterday, {time}", "time.tomorrow_and_time": "Tomorrow, {time}", "time.date_and_time": "{date}, {time}"}
--------------------------------------------------------------------------------
/noterly/assets/i18n/en_US.json:
--------------------------------------------------------------------------------
1 | {"main.action.new": "New note", "main.action.create": "Create", "main.action.save": "Save", "main.action.reactivate": "Reactivate", "page.active_notifications.label": "Active", "page.active_notifications.header.immediate": "Shown immediately", "page.active_notifications.header.scheduled": "Scheduled", "page.active_notifications.header.repeating": "Repeating", "page.active_notifications.empty": "No active notifications.", "page.active_notifications.item.repeats": "Repeats {duration}", "page.active_notifications.item.snoozed": "Snoozed {date_time}", "page.archived_notifications.label": "Archive", "page.archived_notifications.empty": "Archived notifications will appear here.", "page.archived_notifications.button.delete_all": "Delete all archived notifications", "page.archived_notifications.item.archived": "Archived {date_time}", "snackbar.notification_deleted": "Notification \"{title}\" deleted.", "snackbar.notification_archived": "Notification \"{title}\" archived.", "snackbar.all_archived_notifications_deleted": "All archived notifications deleted.", "toast.notification_snoozed": "Notification snoozed for {duration}", "page.settings.title": "Settings", "page.settings.header.notifications": "Notifications", "page.settings.header.system": "System", "page.settings.header.about": "About", "page.settings.header.debug": "Debug options", "page.settings.header.debug.disclaimer": "These options are not supported and may cause issues. Use at your own risk.", "page.settings.notifications.snooze_duration": "Snooze duration", "page.settings.system.notification_settings": "Notification settings", "page.settings.about.version.title": "Version", "page.settings.about.copyright.title": "Copyright & credits", "page.settings.about.copyright.text": "2023 Tom Chapman, TDS Studios.", "page.settings.about.tutorial.title": "Tutorial", "page.settings.about.licenses.title": "Licenses", "page.settings.about.licenses.page.legalese": "Copyright \u00a9 2023 Tom Chapman, TDS Studios.", "page.settings.about.privacy_policy.title": "Privacy policy", "page.settings.about.feedback.title": "Send feedback", "page.settings.about.translate.title": "Help translate Noterly", "page.settings.about.donation.title": "Buy me a coffee", "page.create_notification.title": "Create Notification", "page.create_notification.header.details": "Notification details", "page.create_notification.header.timing": "Timing", "page.create_notification.timing.schedule.title": "Schedule", "page.create_notification.timing.schedule.subtitle": "Send", "page.create_notification.timing.repeat.title": "Repeating", "page.create_notification.timing.repeat.subtitle": "Repeat", "page.create_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.create_notification.details.field.title.label": "Title", "page.create_notification.details.field.title.error": "Please enter a title", "page.create_notification.details.field.body.label": "Body", "page.create_notification.details.field.colour.label": "Color", "page.edit_notification.title": "Edit Notification", "page.edit_notification.header.details": "Notification details", "page.edit_notification.header.timing": "Timing", "page.edit_notification.timing.schedule.title": "Schedule", "page.edit_notification.timing.schedule.subtitle": "Send", "page.edit_notification.timing.repeat.title": "Repeating", "page.edit_notification.timing.repeat.subtitle": "Repeat", "page.edit_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.edit_notification.details.field.title.label": "Title", "page.edit_notification.details.field.title.error": "Please enter a title", "page.edit_notification.details.field.body.label": "Body", "page.edit_notification.details.field.colour.label": "Color", "item.title": "Title", "item.body": "Body", "item.colour": "Color", "notification.action.mark_as_done": "Mark as done", "notification.action.snooze": "Snooze", "notification.action.snooze_for": "Snooze for {duration}", "dialog.picker.date_time.title": "Select date and time", "dialog.picker.date_time.header.date": "Date", "dialog.picker.date_time.header.time": "Time", "dialog.picker.date_time.button.select_date": "Select date", "dialog.picker.date_time.button.select_time": "Select time", "dialog.picker.repetition.title": "Select repeat duration", "dialog.picker.repetition.subtitle": "repeats {duration}", "dialog.picker.repetition.field.number.label": "Number", "dialog.picker.repetition.field.period.label": "Period", "dialog.picker.duration.title": "Select duration", "dialog.picker.duration.subtitle": "{duration}", "dialog.picker.duration.field.number.label": "Number", "dialog.picker.duration.field.period.label": "Period", "dialog.picker.colour.title": "Select color", "dialog.easter_egg.title": "Easter egg!", "dialog.easter_egg.text": "Congratulations, you've found the easter egg! You can now enable debug options. Please note that these options are not supported and may cause issues.", "dialog_easter_egg.action.show_debug_options": "Show debug options", "dialog.about.translations.title": "Translations", "dialog.about.action.show_licenses": "Licenses", "tutorial.page.0.title": "Welcome to Noterly", "tutorial.page.0.subtitle": "Simple notification reminders", "tutorial.page.0.content": "Here's some basic information to get you started.", "tutorial.page.1.title": "Reminders", "tutorial.page.1.content": "Noterly is designed for quick, simple reminders. You may find a to-do list or calendar app more suitable for more complex tasks.", "tutorial.page.2.title": "Create a notification", "tutorial.page.2.content": "Tap the floating button to create a new notification.", "tutorial.page.2.confirmation.0": "That's right!", "tutorial.page.2.confirmation.1": "Well done!", "tutorial.page.2.confirmation.2": "You've got it!", "tutorial.page.2.confirmation.3": "Great job!", "tutorial.page.2.confirmation.4": "Great success!", "tutorial.page.3.title": "Manage notifications", "tutorial.page.3.content": "Completed your task? Swipe the notification away. To delete a notification, go to the archive page and swipe it away.", "tutorial.page.3.example": "Swipe to archive or delete", "tutorial.page.4.title": "Snoozing reminders", "tutorial.page.4.content": "You can snooze a reminder by tapping the button in the notification. Customise the snooze duration in settings.", "tutorial.page.5.title": "That's it!", "tutorial.page.5.content": "You're ready to start using Noterly. You can always come back to this tutorial in settings.", "tutorial.updated_experience_text": "You're seeing this because this tutorial experience has been updated.", "tutorial.action.back": "Back", "tutorial.action.next": "Next", "tutorial.action.done": "Done", "tutorial.action.skip": "Skip", "general.ok": "OK", "general.cancel": "Cancel", "general.undo": "Undo", "general.close": "Close", "time.second": "second", "time.seconds": "seconds", "time.seconds.value": "{value} seconds", "time.minute": "minute", "time.minutes": "minutes", "time.minutes.value": "{value} minutes", "time.hour": "hour", "time.hours": "hours", "time.hours.value": "{value} hours", "time.day": "day", "time.days": "days", "time.days.value": "{value} days", "time.week": "week", "time.weeks": "weeks", "time.weeks.value": "{value} weeks", "time.month": "month", "time.months": "months", "time.months.value": "{value} months", "time.year": "year", "time.years": "years", "time.years.value": "{value} years", "time.hourly": "hourly", "time.daily": "daily", "time.weekly": "weekly", "time.monthly": "monthly", "time.yearly": "yearly", "time.repetition.every.value": "every {number} {type}", "time.snooze.until": "until {date_time}", "time.today_and_time": "Today, {time}", "time.yesterday_and_time": "Yesterday, {time}", "time.tomorrow_and_time": "Tomorrow, {time}", "time.date_and_time": "{date}, {time}"}
--------------------------------------------------------------------------------
/scripts/i18n/out/en_GB.json:
--------------------------------------------------------------------------------
1 | {"main.action.new": "New note", "main.action.create": "Create", "main.action.save": "Save", "main.action.reactivate": "Reactivate", "page.active_notifications.label": "Active", "page.active_notifications.header.immediate": "Shown immediately", "page.active_notifications.header.scheduled": "Scheduled", "page.active_notifications.header.repeating": "Repeating", "page.active_notifications.empty": "No active notifications.", "page.active_notifications.item.repeats": "Repeats {duration}", "page.active_notifications.item.snoozed": "Snoozed {date_time}", "page.archived_notifications.label": "Archive", "page.archived_notifications.empty": "Archived notifications will appear here.", "page.archived_notifications.button.delete_all": "Delete all archived notifications", "page.archived_notifications.item.archived": "Archived {date_time}", "snackbar.notification_deleted": "Notification \"{title}\" deleted.", "snackbar.notification_archived": "Notification \"{title}\" archived.", "snackbar.all_archived_notifications_deleted": "All archived notifications deleted.", "toast.notification_snoozed": "Notification snoozed for {duration}", "page.settings.title": "Settings", "page.settings.header.notifications": "Notifications", "page.settings.header.system": "System", "page.settings.header.about": "About", "page.settings.header.debug": "Debug options", "page.settings.header.debug.disclaimer": "These options are not supported and may cause issues. Use at your own risk.", "page.settings.notifications.snooze_duration": "Snooze duration", "page.settings.system.notification_settings": "Notification settings", "page.settings.about.version.title": "Version", "page.settings.about.copyright.title": "Copyright & credits", "page.settings.about.copyright.text": "2023 Tom Chapman, TDS Studios.", "page.settings.about.tutorial.title": "Tutorial", "page.settings.about.licenses.title": "Licenses", "page.settings.about.licenses.page.legalese": "Copyright \u00a9 2023 Tom Chapman, TDS Studios.", "page.settings.about.privacy_policy.title": "Privacy policy", "page.settings.about.feedback.title": "Send feedback", "page.settings.about.translate.title": "Help translate Noterly", "page.settings.about.donation.title": "Buy me a coffee", "page.create_notification.title": "Create Notification", "page.create_notification.header.details": "Notification details", "page.create_notification.header.timing": "Timing", "page.create_notification.timing.schedule.title": "Schedule", "page.create_notification.timing.schedule.subtitle": "Send", "page.create_notification.timing.repeat.title": "Repeating", "page.create_notification.timing.repeat.subtitle": "Repeat", "page.create_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.create_notification.details.field.title.label": "Title", "page.create_notification.details.field.title.error": "Please enter a title", "page.create_notification.details.field.body.label": "Body", "page.create_notification.details.field.colour.label": "Colour", "page.edit_notification.title": "Edit Notification", "page.edit_notification.header.details": "Notification details", "page.edit_notification.header.timing": "Timing", "page.edit_notification.timing.schedule.title": "Schedule", "page.edit_notification.timing.schedule.subtitle": "Send", "page.edit_notification.timing.repeat.title": "Repeating", "page.edit_notification.timing.repeat.subtitle": "Repeat", "page.edit_notification.timing.repeat.info": "Repeating notifications will only repeat once they are marked as done from the notification.", "page.edit_notification.details.field.title.label": "Title", "page.edit_notification.details.field.title.error": "Please enter a title", "page.edit_notification.details.field.body.label": "Body", "page.edit_notification.details.field.colour.label": "Colour", "item.title": "Title", "item.body": "Body", "item.colour": "Colour", "notification.action.mark_as_done": "Mark as done", "notification.action.snooze": "Snooze", "notification.action.snooze_for": "Snooze for {duration}", "dialog.picker.date_time.title": "Select date and time", "dialog.picker.date_time.header.date": "Date", "dialog.picker.date_time.header.time": "Time", "dialog.picker.date_time.button.select_date": "Select date", "dialog.picker.date_time.button.select_time": "Select time", "dialog.picker.repetition.title": "Select repeat duration", "dialog.picker.repetition.subtitle": "repeats {duration}", "dialog.picker.repetition.field.number.label": "Number", "dialog.picker.repetition.field.period.label": "Period", "dialog.picker.duration.title": "Select duration", "dialog.picker.duration.subtitle": "{duration}", "dialog.picker.duration.field.number.label": "Number", "dialog.picker.duration.field.period.label": "Period", "dialog.picker.colour.title": "Select colour", "dialog.easter_egg.title": "Easter egg!", "dialog.easter_egg.text": "Congratulations, you've found the easter egg! You can now enable debug options. Please note that these options are not supported and may cause issues.", "dialog_easter_egg.action.show_debug_options": "Show debug options", "dialog.about.translations.title": "Translations", "dialog.about.action.show_licenses": "Licenses", "tutorial.page.0.title": "Welcome to Noterly", "tutorial.page.0.subtitle": "Simple notification reminders", "tutorial.page.0.content": "Here's some basic information to get you started.", "tutorial.page.1.title": "Reminders", "tutorial.page.1.content": "Noterly is designed for quick, simple reminders. You may find a to-do list or calendar app more suitable for more complex tasks.", "tutorial.page.2.title": "Create a notification", "tutorial.page.2.content": "Tap the floating button to create a new notification.", "tutorial.page.2.confirmation.0": "That's right!", "tutorial.page.2.confirmation.1": "Well done!", "tutorial.page.2.confirmation.2": "You've got it!", "tutorial.page.2.confirmation.3": "Great job!", "tutorial.page.2.confirmation.4": "Great success!", "tutorial.page.3.title": "Manage notifications", "tutorial.page.3.content": "Completed your task? Swipe the notification away. To delete a notification, go to the archive page and swipe it away.", "tutorial.page.3.example": "Swipe to archive or delete", "tutorial.page.4.title": "Snoozing reminders", "tutorial.page.4.content": "You can snooze a reminder by tapping the button in the notification. Customise the snooze duration in settings.", "tutorial.page.5.title": "That's it!", "tutorial.page.5.content": "You're ready to start using Noterly. You can always come back to this tutorial in settings.", "tutorial.updated_experience_text": "You're seeing this because this tutorial experience has been updated.", "tutorial.action.back": "Back", "tutorial.action.next": "Next", "tutorial.action.done": "Done", "tutorial.action.skip": "Skip", "general.ok": "OK", "general.cancel": "Cancel", "general.undo": "Undo", "general.close": "Close", "time.second": "second", "time.seconds": "seconds", "time.seconds.value": "{value} seconds", "time.minute": "minute", "time.minutes": "minutes", "time.minutes.value": "{value} minutes", "time.hour": "hour", "time.hours": "hours", "time.hours.value": "{value} hours", "time.day": "day", "time.days": "days", "time.days.value": "{value} days", "time.week": "week", "time.weeks": "weeks", "time.weeks.value": "{value} weeks", "time.month": "month", "time.months": "months", "time.months.value": "{value} months", "time.year": "year", "time.years": "years", "time.years.value": "{value} years", "time.hourly": "hourly", "time.daily": "daily", "time.weekly": "weekly", "time.monthly": "monthly", "time.yearly": "yearly", "time.repetition.every.value": "every {number} {type}", "time.snooze.until": "until {date_time}", "time.today_and_time": "Today, {time}", "time.yesterday_and_time": "Yesterday, {time}", "time.tomorrow_and_time": "Tomorrow, {time}", "time.date_and_time": "{date}, {time}"}
--------------------------------------------------------------------------------