├── 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /website/img/logo/logo_background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 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 | ![A few screenshots of Noterly](assets/figma/github_readme_showcase.svg) 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /website/img/logo/logo_foreground.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /assets/figma/logo_full_circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /assets/figma/logo_full_rounded.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 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 | image/svg+xml 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}"} --------------------------------------------------------------------------------