├── src ├── lib │ ├── utils │ │ ├── app_flavour │ │ │ └── app_flavour.dart │ │ ├── enum │ │ │ ├── app_launch_transition.dart │ │ │ ├── corner_button_type.dart │ │ │ └── leafy_theme_style.dart │ │ ├── log │ │ │ ├── logable_mixin.dart │ │ │ ├── pretty_console_output.dart │ │ │ ├── get_logger.dart │ │ │ ├── file_output.dart │ │ │ └── simple_log_printer.dart │ │ ├── extensions │ │ │ └── iterable_extensions.dart │ │ ├── app_goes_to_background_aware │ │ │ └── app_goes_to_background_aware.dart │ │ ├── preferences │ │ │ └── shared_preferences.dart │ │ └── dialogs │ │ │ └── confirm │ │ │ └── actions_dialog.dart │ ├── database │ │ └── leafy_notes_db │ │ │ ├── src │ │ │ ├── one_to_manys.dart │ │ │ ├── daos.dart │ │ │ ├── models.dart │ │ │ ├── tables.dart │ │ │ ├── repositories │ │ │ │ └── repositories.dart │ │ │ ├── one_to_many │ │ │ │ └── folder_with_notes.dart │ │ │ ├── models │ │ │ │ ├── note │ │ │ │ │ ├── table │ │ │ │ │ │ ├── sorts.dart │ │ │ │ │ │ └── note_table.dart │ │ │ │ │ ├── repository │ │ │ │ │ │ ├── note_repository.dart │ │ │ │ │ │ └── note_repository_impl.dart │ │ │ │ │ ├── dao │ │ │ │ │ │ └── note_dao.g.dart │ │ │ │ │ └── model │ │ │ │ │ │ ├── note_converter.dart │ │ │ │ │ │ └── note_model.dart │ │ │ │ └── folder │ │ │ │ │ ├── dao │ │ │ │ │ └── folder_dao.g.dart │ │ │ │ │ ├── table │ │ │ │ │ ├── sorts.dart │ │ │ │ │ └── folder_table.dart │ │ │ │ │ ├── repository │ │ │ │ │ └── folder_repository.dart │ │ │ │ │ └── model │ │ │ │ │ ├── folder_converter.dart │ │ │ │ │ └── folder_model.dart │ │ │ └── leafy_notes_db.dart │ │ │ ├── extensions │ │ │ ├── iterable_extensions.dart │ │ │ └── map_extensions.dart │ │ │ └── leafy_notes_database.dart │ ├── main_dev.dart │ ├── main_prod.dart │ ├── module │ │ ├── intro │ │ │ ├── tutorial │ │ │ │ ├── domain │ │ │ │ │ ├── slide_type.dart │ │ │ │ │ └── slide_controller.dart │ │ │ │ ├── tutorial_binding.dart │ │ │ │ └── tutorial_page.dart │ │ │ ├── intro_binding.dart │ │ │ └── intro_page.dart │ │ ├── home │ │ │ ├── home_binding.dart │ │ │ ├── widget │ │ │ │ ├── corner_button │ │ │ │ │ ├── _full_screen_box.dart │ │ │ │ │ ├── _other_apps_list.dart │ │ │ │ │ └── _configured_icon_button.dart │ │ │ │ ├── home_widgets │ │ │ │ │ ├── home_top_widget.dart │ │ │ │ │ ├── time_progress │ │ │ │ │ │ └── time_progress_type.dart │ │ │ │ │ └── home_date.dart │ │ │ │ ├── google_search │ │ │ │ │ ├── google_search_input.dart │ │ │ │ │ └── google_search_results.dart │ │ │ │ ├── horizontal_swipe_app_icon.dart │ │ │ │ ├── user_app_button.dart │ │ │ │ └── curved_background.dart │ │ │ └── utils │ │ │ │ └── gesture_processer.dart │ │ ├── startup │ │ │ ├── startup_binding.dart │ │ │ ├── startup_page.dart │ │ │ └── startup_controller.dart │ │ ├── home_notes │ │ │ └── notes │ │ │ │ ├── folders │ │ │ │ ├── home_note_folders_binding.dart │ │ │ │ ├── widget │ │ │ │ │ ├── title │ │ │ │ │ │ └── home_note_folders_title.dart │ │ │ │ │ └── list │ │ │ │ │ │ └── home_note_folder_container.dart │ │ │ │ └── home_note_folders_page.dart │ │ │ │ ├── notes │ │ │ │ ├── home_notes_binding.dart │ │ │ │ ├── widget │ │ │ │ │ ├── list │ │ │ │ │ │ ├── home_notes_empty_widget.dart │ │ │ │ │ │ └── home_note_container.dart │ │ │ │ │ └── title │ │ │ │ │ │ └── home_notes_title.dart │ │ │ │ └── home_notes_page.dart │ │ │ │ └── note │ │ │ │ ├── home_note_binding.dart │ │ │ │ ├── home_note_page.dart │ │ │ │ └── widget │ │ │ │ └── body │ │ │ │ └── home_note_body.dart │ │ ├── home_settings │ │ │ ├── home_settings_binding.dart │ │ │ ├── oss │ │ │ │ ├── home_settings_oss_binding.dart │ │ │ │ ├── home_settings_oss_page.dart │ │ │ │ ├── title │ │ │ │ │ └── home_settings_oss_title.dart │ │ │ │ ├── home_settings_oss_controller.dart │ │ │ │ └── body │ │ │ │ │ └── settings_oss_body.dart │ │ │ ├── about │ │ │ │ ├── home_settings_about_binding.dart │ │ │ │ ├── body │ │ │ │ │ ├── oss │ │ │ │ │ │ └── oss.dart │ │ │ │ │ ├── info │ │ │ │ │ │ ├── gmail.dart │ │ │ │ │ │ ├── github.dart │ │ │ │ │ │ └── telegram.dart │ │ │ │ │ └── settings_about_body.dart │ │ │ │ ├── home_settings_about_page.dart │ │ │ │ ├── title │ │ │ │ │ └── home_settings_about_title.dart │ │ │ │ └── home_settings_about_controller.dart │ │ │ ├── widgets │ │ │ │ ├── home_settings_widgets_binding.dart │ │ │ │ ├── home_settings_widgets_page.dart │ │ │ │ ├── title │ │ │ │ │ └── home_settings_widgets_title.dart │ │ │ │ ├── widget │ │ │ │ │ └── leafy_section_enabled_state_item.dart │ │ │ │ ├── body │ │ │ │ │ ├── time_progress │ │ │ │ │ │ ├── time_progress_type_state.dart │ │ │ │ │ │ └── time_progress_enabled_state.dart │ │ │ │ │ ├── clock │ │ │ │ │ │ └── clock_enabled_state.dart │ │ │ │ │ ├── calendar │ │ │ │ │ │ └── calendar_enabled_state.dart │ │ │ │ │ └── corner_apps │ │ │ │ │ │ ├── left_corner_app_enabled_state.dart │ │ │ │ │ │ └── right_corner_app_enabled_state.dart │ │ │ │ └── home_settings_widgets_controller.dart │ │ │ ├── widget │ │ │ │ ├── settings_body │ │ │ │ │ ├── about │ │ │ │ │ │ └── about.dart │ │ │ │ │ ├── common │ │ │ │ │ │ ├── theme.dart │ │ │ │ │ │ ├── language.dart │ │ │ │ │ │ └── vibration.dart │ │ │ │ │ ├── widget │ │ │ │ │ │ └── theme.dart │ │ │ │ │ ├── take_tutorial │ │ │ │ │ │ └── take_tutorial.dart │ │ │ │ │ ├── home_widgets │ │ │ │ │ │ └── home_widgets.dart │ │ │ │ │ ├── select_default_launcher │ │ │ │ │ │ └── select_default_launcher.dart │ │ │ │ │ └── swipe_apps │ │ │ │ │ │ ├── swipe_to_left_app.dart │ │ │ │ │ │ └── swipe_to_right_app.dart │ │ │ │ └── settings_title │ │ │ │ │ └── settings_title.dart │ │ │ ├── oss_license │ │ │ │ ├── home_settings_oss_license_binding.dart │ │ │ │ ├── title │ │ │ │ │ └── home_settings_oss_license_title.dart │ │ │ │ ├── home_settings_oss_license_page.dart │ │ │ │ ├── home_settings_oss_license_controller.dart │ │ │ │ └── body │ │ │ │ │ └── settings_oss_license_body.dart │ │ │ ├── home_settings_page.dart │ │ │ └── home_settings_controller.dart │ │ └── app_picker │ │ │ ├── app_picker_controller.dart │ │ │ ├── app_picker_binding.dart │ │ │ └── widget │ │ │ ├── app_picker_button.dart │ │ │ └── app_picker_fade.dart │ ├── services │ │ ├── applications │ │ │ ├── application.dart │ │ │ ├── leafy_application.dart │ │ │ ├── exceptions │ │ │ │ ├── app_is_not_in_the_list_exception.dart │ │ │ │ └── unable_to_uninstall_a_system_app_exception.dart │ │ │ └── installed_application.dart │ │ ├── device_vibration │ │ │ └── device_vibration.dart │ │ ├── oss_licenses │ │ │ ├── oss_license.dart │ │ │ └── oss_licenses_service.dart │ │ ├── share │ │ │ └── share_service.dart │ │ ├── home_button_listener │ │ │ └── home_button_listener.dart │ │ ├── logging │ │ │ └── file_logger.dart │ │ ├── toast │ │ │ └── toast_service.dart │ │ ├── date_changed │ │ │ └── date_changed_listener.dart │ │ ├── google_search │ │ │ └── google_search.dart │ │ ├── device_locale │ │ │ └── device_locale_changed_listener.dart │ │ ├── app_environment │ │ │ └── app_environment.dart │ │ ├── platform_methods │ │ │ └── platform_methods_service.dart │ │ └── file_system │ │ │ └── file_system.dart │ ├── resources │ │ ├── theme │ │ │ ├── leafy_theme_constants.dart │ │ │ └── theme_creators.dart │ │ ├── assets │ │ │ └── leafy_icons.dart │ │ ├── localization │ │ │ └── l10n_provider.dart │ │ ├── app_constants.dart │ │ └── settings │ │ │ └── vibration_preferences.dart │ ├── shared_widget │ │ ├── loader.dart │ │ ├── section │ │ │ ├── leafy_section_lib.dart │ │ │ └── src │ │ │ │ ├── list │ │ │ │ └── leafy_section_list_separator.dart │ │ │ │ ├── items │ │ │ │ ├── values │ │ │ │ │ ├── leafy_section_text_value.dart │ │ │ │ │ └── leafy_section_chevron_value.dart │ │ │ │ ├── leafy_section_text_item.dart │ │ │ │ └── leafy_section_custom_item.dart │ │ │ │ ├── section │ │ │ │ ├── widget │ │ │ │ │ ├── leafy_section_footer.dart │ │ │ │ │ └── leafy_section_header.dart │ │ │ │ └── leafy_section.dart │ │ │ │ └── theme │ │ │ │ └── leafy_section_theme.dart │ │ ├── themed_state.dart │ │ ├── themed_widget.dart │ │ ├── themed_get_widget.dart │ │ ├── leafy_spacer.dart │ │ ├── list │ │ │ ├── dismissible_delete_background.dart │ │ │ └── list_builder.dart │ │ └── context_menu │ │ │ └── context_menu_button.dart │ ├── applications │ │ ├── notes │ │ │ └── leafy_notes_routes.dart │ │ └── launcher │ │ │ └── app_routes.dart │ ├── main.dart │ └── base │ │ ├── controller │ │ └── controller_base.dart │ │ └── page │ │ ├── page_base.dart │ │ └── status_page_base.dart ├── android │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── leafy_icon-playstore.png │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── leafy_icon.png │ │ │ │ │ │ └── leafy_icon_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── leafy_icon.png │ │ │ │ │ │ └── leafy_icon_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── leafy_icon.png │ │ │ │ │ │ └── leafy_icon_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── leafy_icon.png │ │ │ │ │ │ └── leafy_icon_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── leafy_icon.png │ │ │ │ │ │ └── leafy_icon_round.png │ │ │ │ │ ├── values │ │ │ │ │ │ ├── leafy_icon_background.xml │ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── drawable │ │ │ │ │ │ ├── launch_background.xml │ │ │ │ │ │ ├── splash_background_gradient.xml │ │ │ │ │ │ └── leafy_notes_launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ ├── launch_background.xml │ │ │ │ │ │ └── leafy_notes_launch_background.xml │ │ │ │ │ ├── anim │ │ │ │ │ │ ├── app_launch_fade_out.xml │ │ │ │ │ │ ├── app_launch_fade_in.xml │ │ │ │ │ │ ├── app_launch_fade_in_long.xml │ │ │ │ │ │ ├── app_launch_fade_out_long.xml │ │ │ │ │ │ ├── app_launch_left.xml │ │ │ │ │ │ └── app_launch_right.xml │ │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ │ ├── leafy_icon.xml │ │ │ │ │ │ └── leafy_icon_round.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ └── kotlin │ │ │ │ │ └── com │ │ │ │ │ └── nivisi │ │ │ │ │ └── leafy_launcher │ │ │ │ │ ├── broadcast_receivers │ │ │ │ │ ├── DeviceLocaleChangedBroadcastReceiver.kt │ │ │ │ │ └── AppChangeReceiver.kt │ │ │ │ │ ├── installed_packages │ │ │ │ │ └── LauncherAppsCallback.kt │ │ │ │ │ ├── NotesActivity.kt │ │ │ │ │ └── LeafyActivityBase.kt │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── proguard-rules.pro │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── assets │ └── icons │ │ ├── web.svg │ │ ├── chevron_right.svg │ │ ├── telegram.svg │ │ ├── github.svg │ │ └── gmail.svg ├── .vscode │ └── launch.json ├── .gitignore ├── pubspec.yaml └── analysis_options.yaml ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── ---feature-request.md │ ├── ---maintenance.md │ ├── ---custom.md │ ├── ---localization.md │ └── ---bug-report.md └── workflows │ └── leafy_maintenance_issues.yml ├── .gitignore └── README.md /src/lib/utils/app_flavour/app_flavour.dart: -------------------------------------------------------------------------------- 1 | enum AppFlavour { 2 | dev, 3 | prod, 4 | } 5 | -------------------------------------------------------------------------------- /src/lib/utils/enum/app_launch_transition.dart: -------------------------------------------------------------------------------- 1 | enum AppLaunchTransition { 2 | left, 3 | right, 4 | fade, 5 | } 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Resolves # 2 | 3 | --- 4 | 5 | _If needed, leave «what», «why» and «why for» notes here._ 6 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/one_to_manys.dart: -------------------------------------------------------------------------------- 1 | library one_to_manys; 2 | 3 | export 'one_to_many/folder_with_notes.dart'; 4 | -------------------------------------------------------------------------------- /src/android/app/src/main/leafy_icon-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/leafy_icon-playstore.png -------------------------------------------------------------------------------- /src/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | extra-gen-snapshot-options=--obfuscate -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/daos.dart: -------------------------------------------------------------------------------- 1 | library daos; 2 | 3 | export 'models/folder/dao/folder_dao.dart'; 4 | export 'models/note/dao/note_dao.dart'; 5 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-hdpi/leafy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-hdpi/leafy_icon.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-mdpi/leafy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-mdpi/leafy_icon.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xhdpi/leafy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xhdpi/leafy_icon.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xxhdpi/leafy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xxhdpi/leafy_icon.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xxxhdpi/leafy_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xxxhdpi/leafy_icon.png -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models.dart: -------------------------------------------------------------------------------- 1 | library models; 2 | 3 | export 'models/folder/model/folder_model.dart'; 4 | export 'models/note/model/note_model.dart'; 5 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/tables.dart: -------------------------------------------------------------------------------- 1 | library tables; 2 | 3 | export 'models/folder/table/folder_table.dart'; 4 | export 'models/note/table/note_table.dart'; 5 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-hdpi/leafy_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-hdpi/leafy_icon_round.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-mdpi/leafy_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-mdpi/leafy_icon_round.png -------------------------------------------------------------------------------- /src/lib/main_dev.dart: -------------------------------------------------------------------------------- 1 | import 'main.dart'; 2 | import 'utils/app_flavour/app_flavour.dart'; 3 | 4 | @pragma('vm:entry-point') 5 | void main() => mainCommon(AppFlavour.dev); 6 | -------------------------------------------------------------------------------- /src/lib/main_prod.dart: -------------------------------------------------------------------------------- 1 | import 'main.dart'; 2 | import 'utils/app_flavour/app_flavour.dart'; 3 | 4 | @pragma('vm:entry-point') 5 | void main() => mainCommon(AppFlavour.prod); 6 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xhdpi/leafy_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xhdpi/leafy_icon_round.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xxhdpi/leafy_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xxhdpi/leafy_icon_round.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-xxxhdpi/leafy_icon_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nivisi/LeafyLauncher/HEAD/src/android/app/src/main/res/mipmap-xxxhdpi/leafy_icon_round.png -------------------------------------------------------------------------------- /src/android/app/src/main/res/values/leafy_icon_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1B2137 4 | -------------------------------------------------------------------------------- /src/lib/module/intro/tutorial/domain/slide_type.dart: -------------------------------------------------------------------------------- 1 | enum SlideType { 2 | quickLaunch, 3 | horizontalSwipes, 4 | cornerButtons, 5 | appPicker, 6 | search, 7 | settings, 8 | } 9 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1B2137 4 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/repositories/repositories.dart: -------------------------------------------------------------------------------- 1 | library repositories; 2 | 3 | export '../models/folder/repository/folder_repository.dart'; 4 | export '../models/note/repository/note_repository.dart'; 5 | -------------------------------------------------------------------------------- /src/lib/services/applications/application.dart: -------------------------------------------------------------------------------- 1 | abstract class Application { 2 | const Application({ 3 | required this.name, 4 | required this.package, 5 | }); 6 | 7 | final String name; 8 | final String package; 9 | } 10 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/module/home/home_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_controller.dart'; 4 | 5 | class HomeBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.put(HomeController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/services/device_vibration/device_vibration.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | class DeviceVibration { 4 | const DeviceVibration(); 5 | 6 | Future weak() async { 7 | return HapticFeedback.selectionClick(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/resources/theme/leafy_theme_constants.dart: -------------------------------------------------------------------------------- 1 | const kBodyText1FontSize = 30.0; 2 | const kBodyText2FontSize = 24.0; 3 | const kBodyText3FontSize = 20.0; 4 | const kBodyText4FontSize = 16.0; 5 | const kBodyText5FontSize = 18.0; 6 | const kBodyText6FontSize = 13.0; 7 | -------------------------------------------------------------------------------- /src/lib/services/applications/leafy_application.dart: -------------------------------------------------------------------------------- 1 | import 'application.dart'; 2 | 3 | class LeafyApplication extends Application { 4 | const LeafyApplication({ 5 | required String name, 6 | required String package, 7 | }) : super(name: name, package: package); 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/module/startup/startup_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'startup_controller.dart'; 4 | 5 | class StartupBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut(() => StartupController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/services/applications/exceptions/app_is_not_in_the_list_exception.dart: -------------------------------------------------------------------------------- 1 | class AppIsNotInTheListException implements Exception { 2 | const AppIsNotInTheListException({ 3 | this.message = 'App was already removed from the list', 4 | }); 5 | 6 | final String message; 7 | } 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📑 Contribution Guidelines 4 | url: https://github.com/nivisi/LeafyLauncher/wiki/Contribution-Guidelines#issues 5 | about: Please check out contribution guidelines before opening an issue. 6 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/one_to_many/folder_with_notes.dart: -------------------------------------------------------------------------------- 1 | import '../../leafy_notes_database.dart'; 2 | 3 | class FolderWithNotes { 4 | FolderWithNotes({required this.folder, required this.notes}); 5 | 6 | final FolderModel folder; 7 | final List notes; 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/services/applications/exceptions/unable_to_uninstall_a_system_app_exception.dart: -------------------------------------------------------------------------------- 1 | class UninstallSystemAppException implements Exception { 2 | const UninstallSystemAppException({ 3 | this.message = 'Unable to uninstall a system app', 4 | }); 5 | 6 | final String message; 7 | } 8 | -------------------------------------------------------------------------------- /src/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 09 19:39:40 EET 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /src/lib/shared_widget/loader.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Loader extends StatelessWidget { 4 | const Loader({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return const CircularProgressIndicator(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/folders/home_note_folders_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_note_folders_controller.dart'; 4 | 5 | class HomeNoteFoldersBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | Get.put(HomeNoteFoldersController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-anydpi-v26/leafy_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/home_settings_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_settings_controller.dart'; 4 | 5 | class HomeSettingsBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut(() => HomeSettingsController()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_fade_in_long.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_fade_out_long.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/mipmap-anydpi-v26/leafy_icon_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/corner_button/_full_screen_box.dart: -------------------------------------------------------------------------------- 1 | part of 'corner_button.dart'; 2 | 3 | class _FullScreenBox extends StatelessWidget { 4 | const _FullScreenBox({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Container(color: Colors.transparent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/drawable/splash_background_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/leafy_section_lib.dart: -------------------------------------------------------------------------------- 1 | library leafy_section; 2 | 3 | export 'src/items/leafy_section_custom_item.dart'; 4 | export 'src/items/leafy_section_text_item.dart'; 5 | export 'src/items/values/leafy_section_chevron_value.dart'; 6 | export 'src/items/values/leafy_section_text_value.dart'; 7 | export 'src/section/leafy_section.dart'; 8 | -------------------------------------------------------------------------------- /src/lib/services/applications/installed_application.dart: -------------------------------------------------------------------------------- 1 | import 'application.dart'; 2 | 3 | class InstalledApplication extends Application { 4 | const InstalledApplication({ 5 | required String name, 6 | required String package, 7 | required this.isSystem, 8 | }) : super(name: name, package: package); 9 | 10 | final bool isSystem; 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/resources/assets/leafy_icons.dart: -------------------------------------------------------------------------------- 1 | class LeafyIcons { 2 | static const _path = 'assets/icons'; 3 | 4 | static const chevronRight = '$_path/chevron_right.svg'; 5 | static const github = '$_path/github.svg'; 6 | static const gmail = '$_path/gmail.svg'; 7 | static const telegram = '$_path/telegram.svg'; 8 | static const web = '$_path/web.svg'; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/table/sorts.dart: -------------------------------------------------------------------------------- 1 | part of '../dao/note_dao.dart'; 2 | 3 | OrderingTerm _notesByLastEditedAt($NotesTable table) { 4 | return OrderingTerm.desc(table.lastEditedAt); 5 | } 6 | 7 | // OrderingTerm _notesByTitleOrFirstLine($NotesTable table) { 8 | // return OrderingTerm.asc(coalesce([table.title, table.firstLine])); 9 | // } 10 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss/home_settings_oss_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_settings_oss_controller.dart'; 4 | 5 | class HomeSettingsOssBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeSettingsOssController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/home_settings_about_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_settings_about_controller.dart'; 4 | 5 | class HomeSettingsAboutBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeSettingsAboutController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/lib/services/oss_licenses/oss_license.dart: -------------------------------------------------------------------------------- 1 | class OssLicense { 2 | OssLicense({ 3 | required this.name, 4 | required this.version, 5 | this.description, 6 | required this.license, 7 | this.homepage, 8 | }); 9 | 10 | final String name; 11 | final String version; 12 | final String? description; 13 | final String license; 14 | final String? homepage; 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/home_settings_widgets_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_settings_widgets_controller.dart'; 4 | 5 | class HomeSettingsWidgetsBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut( 9 | () => HomeSettingsWidgetsController(), 10 | ); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/services/share/share_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:share/share.dart'; 4 | 5 | class ShareService { 6 | Future shareText(String text, {String? subject}) { 7 | return Share.share(text, subject: subject); 8 | } 9 | 10 | Future shareFile(File file, {String? subject}) { 11 | return Share.shareFiles([file.path], subject: subject); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/utils/log/logable_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:logger/logger.dart'; 3 | 4 | import 'get_logger.dart'; 5 | 6 | mixin LogableMixin { 7 | Logger? _logger; 8 | 9 | @protected 10 | Logger get logger => _logger ??= getLogger(forObject: this); 11 | 12 | @override 13 | String toString() { 14 | return runtimeType.toString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/utils/extensions/iterable_extensions.dart: -------------------------------------------------------------------------------- 1 | extension IterableExtensions on Iterable { 2 | T? firstOrNull() { 3 | if (length > 0) { 4 | return first; 5 | } 6 | 7 | return null; 8 | } 9 | 10 | T? firstWhereOrNull(bool Function(T item) condition) { 11 | if (length > 0) { 12 | return firstWhere(condition); 13 | } 14 | 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/extensions/iterable_extensions.dart: -------------------------------------------------------------------------------- 1 | extension IterableExtensions on Iterable { 2 | T? firstOrNull() { 3 | if (length > 0) { 4 | return first; 5 | } 6 | 7 | return null; 8 | } 9 | 10 | T? firstWhereOrNull(bool Function(T item) condition) { 11 | if (length > 0) { 12 | return firstWhere(condition); 13 | } 14 | 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/module/intro/tutorial/tutorial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:leafy_launcher/module/home_settings/home_settings_controller.dart'; 3 | 4 | import 'tutorial_controller.dart'; 5 | 6 | class TutorialBinding implements Bindings { 7 | @override 8 | void dependencies() { 9 | Get.lazyPut(() => TutorialController()); 10 | Get.lazyPut(() => HomeSettingsController()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/drawable-v21/leafy_notes_launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/drawable/leafy_notes_launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/module/intro/intro_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:leafy_launcher/module/home_settings/home_settings_controller.dart'; 3 | import 'package:leafy_launcher/module/intro/intro_controller.dart'; 4 | 5 | class IntroBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | Get.lazyPut(() => IntroController()); 9 | Get.lazyPut(() => HomeSettingsController()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/icons/web.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/repository/note_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../models.dart'; 2 | import '../../../one_to_manys.dart'; 3 | 4 | abstract class NoteRepository { 5 | Stream watchAllNotesOfFolder(String id); 6 | 7 | Future getById(String id); 8 | Future create(FolderModel folder); 9 | Future update(NoteModel folder); 10 | Future delete(NoteModel folder); 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/notes/home_notes_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_notes_controller.dart'; 4 | 5 | class HomeNotesBinding extends Bindings { 6 | @override 7 | void dependencies() { 8 | final folderId = Get.parameters['folderId']; 9 | 10 | if (folderId is! String) { 11 | throw Exception('folderId is not found'); 12 | } 13 | 14 | Get.put(HomeNotesController(folderId)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | ## Flutter wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | # -keep class com.google.firebase.** { *; } // uncomment this if you are using firebase in the project 9 | -dontwarn io.flutter.embedding.** 10 | -ignorewarnings -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/extensions/map_extensions.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_catching_errors 2 | 3 | extension MapExtensions on Map { 4 | TValue? firstWhereKeyOrNull(bool Function(TKey item) condition) { 5 | if (keys.isNotEmpty) { 6 | try { 7 | final key = keys.firstWhere(condition); 8 | 9 | return this[key]; 10 | } on Error { 11 | return null; 12 | } 13 | } 14 | 15 | return null; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/dao/note_dao.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'note_dao.dart'; 4 | 5 | // ************************************************************************** 6 | // DaoGenerator 7 | // ************************************************************************** 8 | 9 | mixin _$NoteDaoMixin on DatabaseAccessor { 10 | $NotesTable get notes => attachedDatabase.notes; 11 | $FoldersTable get folders => attachedDatabase.folders; 12 | } 13 | -------------------------------------------------------------------------------- /src/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 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/dao/folder_dao.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'folder_dao.dart'; 4 | 5 | // ************************************************************************** 6 | // DaoGenerator 7 | // ************************************************************************** 8 | 9 | mixin _$FolderDaoMixin on DatabaseAccessor { 10 | $NotesTable get notes => attachedDatabase.notes; 11 | $FoldersTable get folders => attachedDatabase.folders; 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/body/oss/oss.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_about_body.dart'; 2 | 3 | class _Oss extends ThemedGetWidget { 4 | const _Oss({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsAboutOss), 10 | onTap: controller.openOss, 11 | value: const LeafySectionChevronValue(), 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/about/about.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_body.dart'; 2 | 3 | class _About extends ThemedGetWidget { 4 | const _About({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsAbout), 10 | onTap: controller.openAbout, 11 | value: const LeafySectionChevronValue(), 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/icons/chevron_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/resources/theme/theme_creators.dart: -------------------------------------------------------------------------------- 1 | part of 'leafy_theme.dart'; 2 | 3 | typedef ThemeCreator = S Function({ 4 | required Widget child, 5 | }); 6 | 7 | HomeTheme _homeCreator(Widget child) { 8 | switch (LeafyTheme.currentStyle) { 9 | case LeafyThemeStyle.light: 10 | return HomeTheme.light(child); 11 | case LeafyThemeStyle.dark: 12 | return HomeTheme.dark(child); 13 | default: 14 | throw 'Unknown style'; 15 | } 16 | } 17 | 18 | final _creatorMap = { 19 | HomeTheme: _homeCreator, 20 | }; 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F9E9 Feature request" 3 | about: Suggest a feature for Leafy 4 | title: '' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss_license/home_settings_oss_license_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_settings_oss_license_controller.dart'; 4 | 5 | class HomeSettingsOssLicenseBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | final name = Get.parameters['name']; 9 | 10 | if (name is! String) { 11 | throw Exception('name is not found'); 12 | } 13 | 14 | Get.lazyPut( 15 | () => HomeSettingsOssLicenseController(name), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/common/theme.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_body.dart'; 2 | 3 | class _Theme extends ThemedGetWidget { 4 | const _Theme({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsTheme), 10 | onTap: controller.changeTheme, 11 | value: LeafySectionTextValue(value: theme.style.localize()), 12 | ); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/leafy_maintenance_issues.yml: -------------------------------------------------------------------------------- 1 | name: 🔧 Move Maintenance issues to Maintenance project 2 | 3 | on: 4 | issues: 5 | types: 6 | - labeled 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | 11 | jobs: 12 | AssignMaintenanceProject: 13 | runs-on: ubuntu-latest 14 | if: github.event.label.name == 'maintenance' 15 | steps: 16 | - name: Assign to Maintenance Project 17 | uses: srggrs/assign-one-project-github-action@1.2.1 18 | with: 19 | project: 'https://github.com/nivisi/LeafyLauncher/projects/8' 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---maintenance.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F9F9 Maintenance" 3 | about: Suggest codebase improvements (refactoring, naming etc) 4 | title: '' 5 | labels: maintenance 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/table/sorts.dart: -------------------------------------------------------------------------------- 1 | part of '../dao/folder_dao.dart'; 2 | 3 | OrderingTerm _foldersByIsDefault($FoldersTable table) { 4 | return OrderingTerm( 5 | expression: table.isDefault, 6 | mode: OrderingMode.desc, 7 | ); 8 | } 9 | 10 | OrderingTerm _foldersByLastEditedAt($FoldersTable table) { 11 | return OrderingTerm( 12 | expression: table.lastEditedAt, 13 | mode: OrderingMode.desc, 14 | ); 15 | } 16 | 17 | // OrderingTerm _foldersByTitle($FoldersTable table) { 18 | // return OrderingTerm(expression: table.title); 19 | // } 20 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F3A8 Custom" 3 | about: Provide some feedback or a report, probably not related to the Android Application 4 | codebase 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | 18 | -------------------------------------------------------------------------------- /src/lib/module/app_picker/app_picker_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../services/applications/application.dart'; 4 | import '../../utils/enum/user_selected_app_type.dart'; 5 | import 'app_picker_controller_base.dart'; 6 | 7 | class AppPickerController extends AppPickerControllerBase { 8 | AppPickerController({ 9 | bool selectOnFirstMatch = false, 10 | UserSelectedAppType? type, 11 | }) : super(selectOnFirstMatch: selectOnFirstMatch, type: type); 12 | 13 | @override 14 | Future onAppSelected(Application app) async { 15 | Get.back(result: app); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/body/info/gmail.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_about_body.dart'; 2 | 3 | class _Gmail extends ThemedGetWidget { 4 | const _Gmail({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsAboutOpenGmail), 10 | leading: SvgPicture.asset( 11 | LeafyIcons.gmail, 12 | color: theme.foregroundColor, 13 | ), 14 | onTap: controller.openGmail, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/repository/folder_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../models.dart'; 2 | import '../../../one_to_manys.dart'; 3 | 4 | abstract class FolderRepository { 5 | FolderModel get defaultFolder; 6 | 7 | Stream> watchAllFolderWithNotes(); 8 | 9 | Future getById(String id); 10 | Future create({String? withTitle}); 11 | 12 | Future insert(FolderModel model); 13 | Future update(FolderModel model); 14 | Future delete(FolderModel model); 15 | 16 | Stream watchFolder(String folderId); 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/home_widgets/home_top_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 3 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 4 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 5 | 6 | class HomeTopWidget extends ThemedWidget { 7 | const HomeTopWidget({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget body(BuildContext context, LeafyTheme theme) { 11 | return Icon( 12 | Icons.search, 13 | color: theme.foregroundColor, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/body/info/github.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_about_body.dart'; 2 | 3 | class _GitHub extends ThemedGetWidget { 4 | const _GitHub({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsAboutOpenGithub), 10 | leading: SvgPicture.asset( 11 | LeafyIcons.github, 12 | color: theme.foregroundColor, 13 | ), 14 | onTap: controller.openGithub, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/services/home_button_listener/home_button_listener.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | class HomeButtonListener { 7 | const HomeButtonListener(); 8 | 9 | static const _channel = EventChannel( 10 | 'com.nivisi.leafy_launcher/homePressedChannel', 11 | ); 12 | 13 | static final Stream _stream = _channel.receiveBroadcastStream(); 14 | 15 | StreamSubscription addCallback(VoidCallback callback) { 16 | return _stream.listen( 17 | (_) { 18 | callback(); 19 | }, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/utils/log/pretty_console_output.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logger/logger.dart'; 3 | 4 | class PrettyConsoleOutput extends ConsoleOutput { 5 | static final levelEmojis = { 6 | Level.verbose: '', 7 | Level.debug: '🐛 ', 8 | Level.info: '💡 ', 9 | Level.warning: '⚠️ ', 10 | Level.error: '⛔ ', 11 | Level.wtf: '👾 ', 12 | }; 13 | 14 | @override 15 | void output(OutputEvent event) { 16 | for (var i = 0; i < event.lines.length; i++) { 17 | debugPrint('${i == 0 ? levelEmojis[event.level] : ''}${event.lines[i]}'); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/module/intro/intro_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/base/page/page_base.dart'; 3 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 4 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 5 | 6 | import 'intro_controller.dart'; 7 | import 'widget/hello_and_welcome.dart'; 8 | 9 | class IntroPage extends PageBase { 10 | const IntroPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget pageBody(BuildContext context, LeafyTheme theme) { 14 | return const HelloAndWelcome(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/anim/app_launch_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/body/info/telegram.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_about_body.dart'; 2 | 3 | class _Telegram 4 | extends ThemedGetWidget { 5 | const _Telegram({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget body(BuildContext context, LeafyTheme theme) { 9 | return LeafySectionTextItem( 10 | title: L10nProvider.getText(L10n.settingsAboutOpenTelegram), 11 | leading: SvgPicture.asset( 12 | LeafyIcons.telegram, 13 | color: theme.foregroundColor, 14 | ), 15 | onTap: controller.openTelegram, 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---localization.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F3AD Localization" 3 | about: Request a new language translation or report an incorrect or unclear translations 4 | title: '' 5 | labels: l10n 6 | assignees: '' 7 | 8 | --- 9 | 10 | 17 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/list/leafy_section_list_separator.dart: -------------------------------------------------------------------------------- 1 | part of 'leafy_section_list.dart'; 2 | 3 | class _LeafySectionListSeparator 4 | extends ThemedWidget { 5 | const _LeafySectionListSeparator({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget body(BuildContext context, LeafyTheme theme) { 9 | return Padding( 10 | padding: const EdgeInsets.only( 11 | top: kDefaultPadding * 2.0, 12 | bottom: kDefaultPadding * 2.0, 13 | ), 14 | child: Divider( 15 | height: 1.0, 16 | color: theme.separatorColor, 17 | ), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/services/logging/file_logger.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:get/get.dart'; 4 | import 'package:intl/intl.dart'; 5 | 6 | import '../file_system/file_system.dart'; 7 | 8 | class FileLogger { 9 | final _fileSystem = Get.find(); 10 | final _dateFormatter = DateFormat('dd.MM.yyyy'); 11 | 12 | File getFileForToday() { 13 | final today = DateTime.now(); 14 | final fileName = 15 | '''${_fileSystem.loggingDirectory.path}/log${_dateFormatter.format(today)}.log'''; 16 | final file = File(fileName); 17 | if (!file.existsSync()) { 18 | file.createSync(); 19 | } 20 | return file; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/module/home/utils/gesture_processer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum Direction { left, right, up, down } 4 | 5 | class GestureProcessor { 6 | Offset? startPoint; 7 | Offset? endPoint; 8 | 9 | Direction end() { 10 | if (startPoint == null || endPoint == null) { 11 | throw Exception('Both startPoint and endPoint must be configured'); 12 | } 13 | 14 | final dx = startPoint!.dx - endPoint!.dx; 15 | final dy = startPoint!.dy - endPoint!.dy; 16 | if (dx.abs() > dy.abs()) { 17 | return dx < 0 ? Direction.right : Direction.left; 18 | } 19 | return dy < 0 ? Direction.down : Direction.up; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/module/intro/tutorial/tutorial_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/base/page/page_base.dart'; 3 | import 'package:leafy_launcher/module/intro/tutorial/widget/tutorial.dart'; 4 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 5 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 6 | 7 | import 'tutorial_controller.dart'; 8 | 9 | class TutorialPage extends PageBase { 10 | const TutorialPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget pageBody(BuildContext context, LeafyTheme theme) { 14 | return const Tutorial(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/utils/log/get_logger.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:logger/logger.dart'; 3 | 4 | import 'file_output.dart' as file_output; 5 | import 'pretty_console_output.dart'; 6 | import 'simple_log_printer.dart'; 7 | 8 | file_output.FileOutput _fileOutput = file_output.FileOutput(); 9 | PrettyConsoleOutput _prettyConsoleOutput = PrettyConsoleOutput(); 10 | 11 | Logger getLogger({dynamic forObject}) { 12 | return Logger( 13 | printer: SimpleLogPrinter(forObject: forObject), 14 | output: MultiOutput([_fileOutput, _prettyConsoleOutput]), 15 | filter: ProductionFilter(), 16 | level: kDebugMode ? Level.verbose : Level.info, 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/items/values/leafy_section_text_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 4 | 5 | class LeafySectionTextValue 6 | extends ThemedWidget { 7 | const LeafySectionTextValue({ 8 | Key? key, 9 | required this.value, 10 | }) : super(key: key); 11 | 12 | final String value; 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | return Text( 17 | value, 18 | style: theme.bodyText4.copyWith(color: theme.textInfoColor), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0-alpha01' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/leafy_notes_db.dart: -------------------------------------------------------------------------------- 1 | import 'package:moor_flutter/moor_flutter.dart'; 2 | 3 | import 'daos.dart'; 4 | import 'tables.dart'; 5 | 6 | part 'leafy_notes_db.g.dart'; 7 | 8 | // ignore: non_constant_identifier_names 9 | final LeafyNotesDb = LeafyNotesDatabase(); 10 | 11 | @UseMoor( 12 | tables: [Notes, Folders], 13 | daos: [NoteDao, FolderDao], 14 | ) 15 | class LeafyNotesDatabase extends _$LeafyNotesDatabase { 16 | LeafyNotesDatabase() 17 | : super( 18 | FlutterQueryExecutor.inDatabaseFolder( 19 | path: 'leafy_notes.sqlite', 20 | logStatements: true, 21 | ), 22 | ); 23 | 24 | @override 25 | int get schemaVersion => 1; 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/note/home_note_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import 'home_note_controller.dart'; 4 | 5 | class HomeNoteBinding implements Bindings { 6 | @override 7 | void dependencies() { 8 | final folderId = Get.parameters['folderId']; 9 | 10 | if (folderId is! String) { 11 | throw Exception('folderId is not found'); 12 | } 13 | 14 | final noteId = Get.parameters['noteId']; 15 | 16 | if (noteId is! String) { 17 | throw Exception('noteId is not found'); 18 | } 19 | 20 | Get.lazyPut( 21 | () => HomeNoteController( 22 | folderId: folderId, 23 | noteId: noteId, 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/services/toast/toast_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluttertoast/fluttertoast.dart'; 2 | 3 | class ToastService { 4 | const ToastService(); 5 | 6 | Future short(String text, {bool cancelPrevious = false}) async { 7 | if (cancelPrevious) { 8 | await Fluttertoast.cancel(); 9 | } 10 | 11 | await Fluttertoast.showToast( 12 | msg: text, 13 | toastLength: Toast.LENGTH_SHORT, 14 | ); 15 | } 16 | 17 | Future long(String text, {bool cancelPrevious = false}) async { 18 | if (cancelPrevious) { 19 | await Fluttertoast.cancel(); 20 | } 21 | 22 | await Fluttertoast.showToast( 23 | msg: text, 24 | toastLength: Toast.LENGTH_LONG, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 19 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/section/widget/leafy_section_footer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 4 | 5 | class LeafySectionFooter 6 | extends ThemedWidget { 7 | const LeafySectionFooter({ 8 | Key? key, 9 | required this.title, 10 | }) : super(key: key); 11 | 12 | final String title; 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | return Text( 17 | title, 18 | style: theme.bodyText6.copyWith( 19 | color: theme.textInfoColor, 20 | ), 21 | textAlign: TextAlign.start, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/items/values/leafy_section_chevron_value.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:leafy_launcher/resources/assets/leafy_icons.dart'; 4 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 5 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 6 | 7 | class LeafySectionChevronValue 8 | extends ThemedWidget { 9 | const LeafySectionChevronValue({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget body(BuildContext context, LeafyTheme theme) { 13 | return SvgPicture.asset( 14 | LeafyIcons.chevronRight, 15 | color: theme.textInfoColor, 16 | height: 12, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/applications/notes/leafy_notes_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | class LeafyNotesRoutes { 4 | static const notes = '/folders/:folderId/notes'; 5 | static const note = '/folders/:folderId/notes/:noteId'; 6 | static const folders = '/folders'; 7 | 8 | static Future? toFolders() { 9 | return Get.toNamed(folders); 10 | } 11 | 12 | static Future? toNotes(String folderId) { 13 | final route = notes.replaceFirst(':folderId', folderId); 14 | 15 | return Get.toNamed(route); 16 | } 17 | 18 | static Future? toNote(String folderId, String noteId) { 19 | final route = note 20 | .replaceFirst(':folderId', folderId) 21 | .replaceFirst(':noteId', noteId); 22 | 23 | return Get.toNamed(route); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/module/startup/startup_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../base/page/status_page_base.dart'; 4 | import '../../resources/theme/home_theme.dart'; 5 | import '../../resources/theme/leafy_theme.dart'; 6 | import 'startup_controller.dart'; 7 | 8 | class StartupPage extends StatusPageBase { 9 | const StartupPage(); 10 | 11 | @override 12 | Widget loading(BuildContext context, LeafyTheme theme) { 13 | return Center( 14 | child: Text( 15 | 'Loading applications ...', 16 | style: theme.bodyText3, 17 | ), 18 | ); 19 | } 20 | 21 | @override 22 | Widget ready(BuildContext context, LeafyTheme theme) { 23 | return const Center(child: Text('Im the startup page')); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/services/date_changed/date_changed_listener.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class DateChangedListener { 4 | DateChangedListener() { 5 | init(); 6 | } 7 | 8 | late Timer _timer; 9 | 10 | DateTime _lastKnownDate = DateTime.now(); 11 | 12 | final _controller = StreamController.broadcast(); 13 | Stream get onDateChanged => _controller.stream; 14 | 15 | void init() { 16 | _timer = Timer.periodic( 17 | const Duration(seconds: 1), 18 | (timer) { 19 | final now = DateTime.now(); 20 | 21 | if (_lastKnownDate.day != now.day) { 22 | _controller.add(null); 23 | } 24 | 25 | _lastKnownDate = now; 26 | }, 27 | ); 28 | } 29 | 30 | void dispose() { 31 | _timer.cancel(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/section/widget/leafy_section_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 4 | 5 | class LeafySectionHeader 6 | extends ThemedWidget { 7 | const LeafySectionHeader({ 8 | Key? key, 9 | required this.title, 10 | }) : super(key: key); 11 | 12 | final String title; 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | return Text( 17 | title.toUpperCase(), 18 | style: theme.bodyText6.copyWith( 19 | color: theme.textInfoColor, 20 | ), 21 | textAlign: TextAlign.start, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/utils/log/file_output.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:get/get.dart'; 5 | import 'package:logger/logger.dart'; 6 | import 'package:synchronized/synchronized.dart'; 7 | 8 | import '../../services/logging/file_logger.dart'; 9 | 10 | class FileOutput extends LogOutput { 11 | final _fileLogService = Get.find(); 12 | 13 | final _lock = Lock(); 14 | 15 | Future write(OutputEvent event) { 16 | final file = _fileLogService.getFileForToday(); 17 | return file.writeAsString( 18 | '${event.lines.join('\n')}\n', 19 | mode: FileMode.append, 20 | ); 21 | } 22 | 23 | @override 24 | void output(OutputEvent event) { 25 | _lock.synchronized(() { 26 | return write(event); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/utils/app_goes_to_background_aware/app_goes_to_background_aware.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppGoesToBackgroundListener extends WidgetsBindingObserver { 4 | AppGoesToBackgroundListener(this._onGoesToBackground) { 5 | WidgetsBinding.instance!.addObserver(this); 6 | } 7 | 8 | bool _disposed = false; 9 | VoidCallback? _onGoesToBackground; 10 | 11 | @override 12 | void didChangeAppLifecycleState(AppLifecycleState state) { 13 | if (!_disposed && state == AppLifecycleState.paused) { 14 | _onGoesToBackground?.call(); 15 | } 16 | } 17 | 18 | void dispose() { 19 | if (!_disposed) { 20 | WidgetsBinding.instance!.removeObserver(this); 21 | _disposed = true; 22 | _onGoesToBackground = null; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/services/google_search/google_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import '../../utils/log/logable_mixin.dart'; 3 | 4 | class GoogleSearch with LogableMixin { 5 | static const _channel = MethodChannel('com.nivisi.leafy_launcher/common'); 6 | 7 | Future launchSearchAndroid(String query) async { 8 | try { 9 | await _channel.invokeMethod( 10 | 'launchSearch', 11 | {'launchQuery': query}, 12 | ); 13 | } on Exception catch (e) { 14 | logger.e('Unable to launch search', e); 15 | } 16 | } 17 | 18 | Future openGoogleInput() async { 19 | try { 20 | await _channel.invokeMethod('launchGoogleSearchInput'); 21 | } on Exception catch (e) { 22 | logger.e('Unable to launch google search input', e); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/model/folder_converter.dart: -------------------------------------------------------------------------------- 1 | import '../../../leafy_notes_db.dart'; 2 | import 'folder_model.dart'; 3 | 4 | Folder folderModelToDb(FolderModel folder) { 5 | return Folder( 6 | id: folder.id, 7 | title: folder.title, 8 | lastEditedAt: folder.lastEditedAt, 9 | createdAt: folder.createdAt, 10 | isDefault: folder.isDefault, 11 | isArchived: folder.isArchived, 12 | isPinned: folder.isPinned, 13 | ); 14 | } 15 | 16 | FolderModel folderModelFromDb(Folder folder) { 17 | return FolderModel( 18 | id: folder.id, 19 | title: folder.title, 20 | lastEditedAt: folder.lastEditedAt, 21 | createdAt: folder.createdAt, 22 | isDefault: folder.isDefault, 23 | isArchived: folder.isArchived, 24 | isPinned: folder.isPinned, 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/table/folder_table.dart: -------------------------------------------------------------------------------- 1 | import 'package:moor_flutter/moor_flutter.dart'; 2 | 3 | @DataClassName('Folder') 4 | class Folders extends Table { 5 | TextColumn get id => text()(); 6 | TextColumn get title => 7 | text().withLength(min: 1, max: 255).withDefault(const Constant(''))(); 8 | DateTimeColumn get createdAt => dateTime()(); 9 | DateTimeColumn get lastEditedAt => dateTime()(); 10 | // TODO: Set a constraint that there can be only 1 default folder. 11 | BoolColumn get isDefault => boolean().withDefault(const Constant(false))(); 12 | BoolColumn get isArchived => boolean().withDefault(const Constant(false))(); 13 | BoolColumn get isPinned => boolean().withDefault(const Constant(false))(); 14 | 15 | @override 16 | Set? get primaryKey => {id}; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/resources/localization/l10n_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:get/get.dart'; 4 | 5 | import 'l10n.dart'; 6 | 7 | part 'l10n_provider_map_en_us.dart'; 8 | part 'l10n_provider_map_ru_ru.dart'; 9 | part 'l10n_provider_map_fr_fr.dart'; 10 | 11 | class L10nProvider extends Translations { 12 | static String getText(String key) { 13 | final localizedString = key.tr; 14 | 15 | return localizedString == key ? 'UNKNOWN: $key' : localizedString; 16 | } 17 | 18 | String _getKey(Locale locale) { 19 | return '${locale.languageCode}_${locale.countryCode}'; 20 | } 21 | 22 | @override 23 | Map> get keys => { 24 | _getKey(L10n.enLocale): _mapEn, 25 | _getKey(L10n.ruLocale): _mapRu, 26 | _getKey(L10n.frLocale): _mapFr, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/common/language.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_body.dart'; 2 | 3 | class _Language extends ThemedGetWidget { 4 | const _Language({Key? key}) : super(key: key); 5 | 6 | @override 7 | Widget body(BuildContext context, LeafyTheme theme) { 8 | return LeafySectionTextItem( 9 | title: L10nProvider.getText(L10n.settingsLanguage), 10 | onTap: controller.changeLocale, 11 | value: GetBuilder( 12 | id: UserApplicationsController.kLanguageBuilder, 13 | init: controller, 14 | builder: (_) { 15 | return LeafySectionTextValue( 16 | value: controller.getLanguageTitle(), 17 | ); 18 | }, 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss_license/title/home_settings_oss_license_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 3 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 4 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 5 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 6 | 7 | import '../home_settings_oss_license_controller.dart'; 8 | 9 | class SettingsOssLicenseTitle 10 | extends ThemedGetWidget { 11 | const SettingsOssLicenseTitle({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget body(BuildContext context, LeafyTheme theme) { 15 | return PageHeader( 16 | title: controller.title, 17 | onTapped: controller.onTitleTapped, 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/shared_widget/themed_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import '../resources/theme/leafy_theme.dart'; 4 | 5 | abstract class ThemedState extends State { 7 | @override 8 | @nonVirtual 9 | Widget build(BuildContext context) { 10 | final theme = context.dependOnInheritedWidgetOfExactType(); 11 | 12 | assert(theme != null); 13 | 14 | if (theme == null) { 15 | return const ColoredBox( 16 | color: Colors.red, 17 | child: Padding( 18 | padding: EdgeInsets.all(8.0), 19 | child: Text('THEME NOT FOUND!'), 20 | ), 21 | ); 22 | } 23 | 24 | return body(context, theme); 25 | } 26 | 27 | Widget body(BuildContext context, TTheme theme); 28 | } 29 | -------------------------------------------------------------------------------- /src/android/app/src/main/kotlin/com/nivisi/leafy_launcher/broadcast_receivers/DeviceLocaleChangedBroadcastReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nivisi.leafy_launcher.broadcast_receivers 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nivisi.leafy_launcher.MainActivity 7 | import com.nivisi.leafy_launcher.NotesActivity 8 | import java.util.* 9 | 10 | class DeviceLocaleChangedBroadcastReceiver : BroadcastReceiver() { 11 | override fun onReceive(context: Context?, intent: Intent?) { 12 | if (intent?.action == Intent.ACTION_LOCALE_CHANGED) { 13 | val languageTag = Locale.getDefault().toLanguageTag() 14 | MainActivity.self?.dispatchDeviceLocaleChangedEvent(languageTag) 15 | NotesActivity.self?.dispatchDeviceLocaleChangedEvent(languageTag) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss/home_settings_oss_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../base/page/status_page_base.dart'; 4 | import '../../../resources/theme/home_theme.dart'; 5 | import '../../../resources/theme/leafy_theme.dart'; 6 | import 'body/settings_oss_body.dart'; 7 | import 'home_settings_oss_controller.dart'; 8 | import 'title/home_settings_oss_title.dart'; 9 | 10 | class HomeSettingsOssPage 11 | extends StatusPageBase { 12 | const HomeSettingsOssPage(); 13 | 14 | @override 15 | bool get resizeToAvoidBottomInset => false; 16 | 17 | @override 18 | Widget ready(BuildContext context, LeafyTheme theme) { 19 | return Column( 20 | children: const [ 21 | SettingsOssTitle(), 22 | Expanded(child: SettingsOssBody()), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/.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": "Leafy Dev", 9 | "request": "launch", 10 | "type": "dart", 11 | "program": "lib/main_dev.dart", 12 | "args": [ 13 | "--flavor", 14 | "dev" 15 | ], 16 | }, 17 | { 18 | "name": "Leafy Prod", 19 | "request": "launch", 20 | "type": "dart", 21 | "program": "lib/main_prod.dart", 22 | "args": [ 23 | "--flavor", 24 | "prod" 25 | ], 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/model/note_converter.dart: -------------------------------------------------------------------------------- 1 | import '../../../leafy_notes_db.dart'; 2 | import 'note_model.dart'; 3 | 4 | NoteModel noteModelFromDb(Note note) { 5 | return NoteModel( 6 | id: note.id, 7 | title: note.title, 8 | lastEditedAt: note.lastEditedAt, 9 | createdAt: note.createdAt, 10 | firstLine: note.firstLine, 11 | data: note.data, 12 | folderId: note.folderId, 13 | isArchived: note.isArchived, 14 | isPinned: note.isPinned, 15 | ); 16 | } 17 | 18 | Note noteModelToDb(NoteModel note) { 19 | return Note( 20 | id: note.id, 21 | title: note.title, 22 | lastEditedAt: note.lastEditedAt, 23 | createdAt: note.createdAt, 24 | firstLine: note.firstLine, 25 | data: note.data, 26 | folderId: note.folderId, 27 | isArchived: note.isArchived, 28 | isPinned: note.isPinned, 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/note/home_note_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/base/page/status_page_base.dart'; 3 | import 'package:leafy_launcher/module/home_notes/notes/note/widget/body/home_note_body.dart'; 4 | import 'package:leafy_launcher/module/home_notes/notes/note/widget/title/home_note_title.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | 8 | import 'home_note_controller.dart'; 9 | 10 | class HomeNotePage extends StatusPageBase { 11 | const HomeNotePage(); 12 | 13 | @override 14 | Widget ready(BuildContext context, LeafyTheme theme) { 15 | return Column( 16 | children: const [ 17 | HomeNoteTitle(), 18 | HomeNoteBody(), 19 | ], 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/shared_widget/themed_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../resources/theme/leafy_theme.dart'; 5 | 6 | abstract class ThemedWidget extends StatelessWidget { 7 | const ThemedWidget({Key? key}) : super(key: key); 8 | 9 | @override 10 | @nonVirtual 11 | Widget build(BuildContext context) { 12 | final theme = context.dependOnInheritedWidgetOfExactType(); 13 | 14 | assert(theme != null); 15 | 16 | if (theme == null) { 17 | return const ColoredBox( 18 | color: Colors.red, 19 | child: Padding( 20 | padding: EdgeInsets.all(8.0), 21 | child: Text('THEME NOT FOUND!'), 22 | ), 23 | ); 24 | } 25 | 26 | return body(context, theme); 27 | } 28 | 29 | Widget body(BuildContext context, LeafyTheme theme); 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/home_settings_about_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../base/page/status_page_base.dart'; 4 | import '../../../resources/theme/home_theme.dart'; 5 | import '../../../resources/theme/leafy_theme.dart'; 6 | import 'body/settings_about_body.dart'; 7 | import 'home_settings_about_controller.dart'; 8 | import 'title/home_settings_about_title.dart'; 9 | 10 | class HomeSettingsAboutPage 11 | extends StatusPageBase { 12 | const HomeSettingsAboutPage(); 13 | 14 | @override 15 | bool get resizeToAvoidBottomInset => false; 16 | 17 | @override 18 | Widget ready(BuildContext context, LeafyTheme theme) { 19 | return Column( 20 | children: const [ 21 | SettingsAboutTitle(), 22 | Expanded(child: SettingsAboutBody()), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/table/note_table.dart: -------------------------------------------------------------------------------- 1 | import 'package:moor_flutter/moor_flutter.dart'; 2 | 3 | @DataClassName('Note') 4 | class Notes extends Table { 5 | TextColumn get id => text()(); 6 | TextColumn get title => text().nullable().withDefault(const Constant(''))(); 7 | TextColumn get firstLine => 8 | text().nullable().withDefault(const Constant(''))(); 9 | TextColumn get data => text().nullable().withDefault(const Constant(''))(); 10 | DateTimeColumn get createdAt => dateTime()(); 11 | DateTimeColumn get lastEditedAt => dateTime()(); 12 | TextColumn get folderId => 13 | text().customConstraint('REFERENCES folders(id)')(); 14 | BoolColumn get isArchived => boolean().withDefault(const Constant(false))(); 15 | BoolColumn get isPinned => boolean().withDefault(const Constant(false))(); 16 | 17 | @override 18 | Set? get primaryKey => {id}; 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/corner_button/_other_apps_list.dart: -------------------------------------------------------------------------------- 1 | part of 'corner_button.dart'; 2 | 3 | class _OtherAppsList extends StatelessWidget { 4 | const _OtherAppsList({ 5 | Key? key, 6 | required this.types, 7 | required this.onPressed, 8 | }) : super(key: key); 9 | 10 | final Function(CornerButtonType type) onPressed; 11 | final Iterable types; 12 | 13 | List _getOtherTypesChildren() { 14 | return types 15 | .map( 16 | (item) => _ConfiguredIconButton( 17 | type: item, 18 | onPressed: () { 19 | onPressed(item); 20 | }, 21 | ), 22 | ) 23 | .toList(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Column( 29 | mainAxisSize: MainAxisSize.min, 30 | children: _getOtherTypesChildren(), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/utils/enum/corner_button_type.dart: -------------------------------------------------------------------------------- 1 | enum CornerButtonType { 2 | phone, 3 | messages, 4 | camera, 5 | } 6 | 7 | const _phone = 'phone'; 8 | const _messages = 'messages'; 9 | const _camera = 'camera'; 10 | 11 | String stringifyCornerButtonType(CornerButtonType type) { 12 | switch (type) { 13 | case CornerButtonType.phone: 14 | return _phone; 15 | case CornerButtonType.messages: 16 | return _messages; 17 | case CornerButtonType.camera: 18 | return _camera; 19 | default: 20 | throw Exception('Unknown type'); 21 | } 22 | } 23 | 24 | CornerButtonType cornerButtonTypeFromString(String str) { 25 | switch (str) { 26 | case _phone: 27 | return CornerButtonType.phone; 28 | case _messages: 29 | return CornerButtonType.messages; 30 | case _camera: 31 | return CornerButtonType.camera; 32 | default: 33 | throw Exception('Unknown!'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/common/vibration.dart: -------------------------------------------------------------------------------- 1 | part of '../settings_body.dart'; 2 | 3 | class _Vibration 4 | extends ThemedGetWidget { 5 | const _Vibration({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget body(BuildContext context, LeafyTheme theme) { 9 | return LeafySectionTextItem( 10 | title: L10nProvider.getText(L10n.settingsVibration), 11 | onTap: controller.toggleVibrationPreferences, 12 | value: GetBuilder( 13 | id: UserApplicationsController.kVibrationPreferencesBuilderKey, 14 | init: controller, 15 | builder: (_) { 16 | return LeafySectionTextValue( 17 | value: localizeVibrationPreferences( 18 | controller.vibrationPreferences, 19 | ), 20 | ); 21 | }, 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/assets/icons/telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | import 'applications/launcher/leafy_launcher.dart'; 5 | import 'applications/notes/leafy_notes.dart'; 6 | import 'utils/app_flavour/app_flavour.dart'; 7 | 8 | const _appChannel = MethodChannel('com.nivisi.leafy_launcher/app'); 9 | 10 | void main() => mainCommon(AppFlavour.dev); 11 | 12 | Future mainCommon(AppFlavour flavour) async { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | 15 | // For a reason we cannot create another entry point for notes, 16 | // so we need to distinguish the app by calling a method channel. 17 | 18 | final app = await _appChannel.invokeMethod('app'); 19 | 20 | switch (app) { 21 | case 'launcher': 22 | return LeafyLauncher.run(flavour); 23 | case 'leafyNotes': 24 | return LeafyNotes.run(flavour); 25 | } 26 | 27 | throw Exception('Tried to launch an unknown app!'); 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.lock 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 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 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss_license/home_settings_oss_license_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 3 | 4 | import '../../../base/page/status_page_base.dart'; 5 | import '../../../resources/theme/leafy_theme.dart'; 6 | import 'body/settings_oss_license_body.dart'; 7 | import 'home_settings_oss_license_controller.dart'; 8 | import 'title/home_settings_oss_license_title.dart'; 9 | 10 | class HomeSettingsOssLicensePage 11 | extends StatusPageBase { 12 | const HomeSettingsOssLicensePage(); 13 | 14 | @override 15 | bool get resizeToAvoidBottomInset => false; 16 | 17 | @override 18 | Widget ready(BuildContext context, LeafyTheme theme) { 19 | return Column( 20 | children: const [ 21 | SettingsOssLicenseTitle(), 22 | Expanded(child: SettingsOssLicenseBody()), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/android/app/src/main/kotlin/com/nivisi/leafy_launcher/broadcast_receivers/AppChangeReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.nivisi.leafy_launcher.broadcast_receivers 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.nivisi.leafy_launcher.MainActivity 7 | 8 | class AppChangeReceiver : BroadcastReceiver() { 9 | 10 | override fun onReceive(context: Context?, intent: Intent?) { 11 | val packageName = intent?.data?.encodedSchemeSpecificPart ?: return 12 | 13 | handle(intent.action, packageName, false) 14 | } 15 | 16 | companion object { 17 | fun handle( 18 | action: String?, 19 | packageName: String, 20 | replacing: Boolean 21 | ) { 22 | val isRemoved = action == Intent.ACTION_PACKAGE_REMOVED 23 | 24 | MainActivity.self?.dispatchAppChangedEvent(packageName, isRemoved) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/home_settings_widgets_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_settings/widgets/title/home_settings_widgets_title.dart'; 3 | 4 | import '../../../base/page/status_page_base.dart'; 5 | import '../../../resources/theme/home_theme.dart'; 6 | import '../../../resources/theme/leafy_theme.dart'; 7 | import 'body/settings_widgets_body.dart'; 8 | import 'home_settings_widgets_controller.dart'; 9 | 10 | class HomeSettingsWidgetsPage 11 | extends StatusPageBase { 12 | const HomeSettingsWidgetsPage(); 13 | 14 | @override 15 | bool get resizeToAvoidBottomInset => false; 16 | 17 | @override 18 | Widget ready(BuildContext context, LeafyTheme theme) { 19 | return Column( 20 | children: const [ 21 | SettingsWidgetsTitle(), 22 | Expanded(child: SettingsWidgetsBody()), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/base/controller/controller_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../utils/log/logable_mixin.dart'; 5 | 6 | abstract class ControllerBase extends GetxController with LogableMixin { 7 | @protected 8 | Future resolveDependencies() async {} 9 | 10 | @protected 11 | @nonVirtual 12 | Future init() async { 13 | logger 14 | ..i('Initializing ...') 15 | ..i('Resolving dependencies ...'); 16 | 17 | await resolveDependencies(); 18 | 19 | logger.i('Initialized!'); 20 | } 21 | 22 | Future back() async { 23 | final canClose = await this.canClose(); 24 | 25 | if (!canClose) { 26 | return false; 27 | } 28 | 29 | Get.back(); 30 | 31 | return false; 32 | } 33 | 34 | @override 35 | void onInit() { 36 | super.onInit(); 37 | 38 | init(); 39 | } 40 | 41 | Future canClose() async { 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/utils/preferences/shared_preferences.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | const kThemeStyleKey = 'themeStyle'; 4 | const kLocaleKey = 'locale'; 5 | const kLocaleAsInSystem = 'localeAsSystem'; 6 | const kLeftCornerButtonType = 'leftCornerButtonType'; 7 | const kRightCornerButtonType = 'rightCornerButtonType'; 8 | const kVibrationPreferences = 'vibrationPreferences'; 9 | const kIsFirstLaunch = 'isFirstLaunch'; 10 | const kIsTimeProgressVisible = 'isTimeProgressVisible'; 11 | const kTimeProgressType = 'kTimeProgressType'; 12 | const kIsCalendarVisible = 'isCalendarVisible'; 13 | const kIsClockVisible = 'isClockVisible'; 14 | const kIsLeftCornerButtonVisible = 'kIsLeftCornerButtonVisible'; 15 | const kIsRightCornerButtonVisible = 'kIsRightCornerButtonVisible'; 16 | 17 | late final SharedPreferences sharedPreferences; 18 | 19 | Future initSharedPreferences() async { 20 | sharedPreferences = await SharedPreferences.getInstance(); 21 | } 22 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | .vscode/* 34 | !.vscode/launch.json 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Android Studio will place build artifacts here 46 | /android/app/debug 47 | /android/app/profile 48 | /android/app/release 49 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/home_settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/app_constants.dart'; 3 | 4 | import '../../base/page/status_page_base.dart'; 5 | import '../../resources/theme/home_theme.dart'; 6 | import '../../resources/theme/leafy_theme.dart'; 7 | import 'home_settings_controller.dart'; 8 | import 'widget/settings_body/settings_body.dart'; 9 | import 'widget/settings_title/settings_title.dart'; 10 | 11 | class HomeSettingsPage 12 | extends StatusPageBase { 13 | const HomeSettingsPage(); 14 | 15 | static const horizontalPadding = kDefaultPadding * 2.0; 16 | 17 | @override 18 | bool get resizeToAvoidBottomInset => false; 19 | 20 | @override 21 | Widget ready(BuildContext context, LeafyTheme theme) { 22 | return Column( 23 | children: const [ 24 | SettingsTitle(), 25 | Expanded(child: SettingsBody()), 26 | ], 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: leafy_launcher 2 | description: A new Flutter project. 3 | 4 | publish_to: 'none' 5 | 6 | version: 1.2.4 7 | 8 | environment: 9 | sdk: ">=2.15.0 <3.0.0" 10 | 11 | dependencies: 12 | assorted_layout_widgets: ^5.2.1 13 | ensure_initialized: ^0.1.0 14 | equatable: ^2.0.3 15 | flutter: 16 | sdk: flutter 17 | flutter_localizations: 18 | sdk: flutter 19 | flutter_oss_licenses: ^1.1.1 20 | flutter_svg: ^1.0.3 21 | fluttertoast: ^8.0.8 22 | get: ^4.5.1 23 | intl: ^0.17.0 24 | lint: ^1.8.1 25 | logger: ^1.0.0 26 | moor_flutter: ^4.0.0 27 | package_info_plus: ^1.3.0 28 | path_provider: ^2.0.2 29 | share: ^2.0.4 30 | shared_preferences: ^2.0.6 31 | synchronized: ^3.0.0 32 | table_calendar: ^3.0.2 33 | url_launcher: ^6.0.18 34 | uuid: ^3.0.5 35 | 36 | dev_dependencies: 37 | build_runner: ^2.1.5 38 | moor_generator: ^4.6.0+1 39 | 40 | flutter: 41 | uses-material-design: true 42 | 43 | assets: 44 | - assets/icons/ 45 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/widget/theme.dart: -------------------------------------------------------------------------------- 1 | // part of '../settings_body.dart'; 2 | 3 | // class _Theme extends ThemedGetWidget { 4 | // const _Theme({Key? key}) : super(key: key); 5 | 6 | // @override 7 | // Widget body(BuildContext context, LeafyTheme theme) { 8 | // return Column( 9 | // crossAxisAlignment: CrossAxisAlignment.start, 10 | // children: [ 11 | // Text( 12 | // L10nProvider.getText(L10n.settingsTheme), 13 | // style: theme.bodyText3.copyWith(color: theme.textInfoColor), 14 | // ), 15 | // const LeafySpacer(), 16 | // TouchableTextButton( 17 | // text: theme.style.localize(), 18 | // style: theme.bodyText2, 19 | // color: theme.foregroundColor, 20 | // pressedColor: theme.foregroundPressedColor, 21 | // onTap: controller.changeTheme, 22 | // ), 23 | // ], 24 | // ); 25 | // } 26 | // } 27 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/title/home_settings_about_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 4 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 5 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 6 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 7 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 8 | 9 | import '../home_settings_about_controller.dart'; 10 | 11 | class SettingsAboutTitle 12 | extends ThemedGetWidget { 13 | const SettingsAboutTitle({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget body(BuildContext context, LeafyTheme theme) { 17 | return PageHeader( 18 | title: L10nProvider.getText(L10n.settingsAboutTitle), 19 | onTapped: controller.onTitleTapped, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/module/app_picker/app_picker_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../utils/enum/user_selected_app_type.dart'; 4 | import 'app_picker_controller.dart'; 5 | import 'app_picker_controller_base.dart'; 6 | 7 | class AppPickerBinding implements Bindings { 8 | @override 9 | void dependencies() { 10 | final typeStr = Get.parameters['type']; 11 | 12 | UserSelectedAppType? type; 13 | 14 | if (typeStr != null) { 15 | type = userSelectedAppTypeFromString(typeStr); 16 | } 17 | 18 | final selectOnFirstMatchStr = 19 | Get.parameters[AppPickerControllerBase.selectOnFirstMatchParameter]; 20 | 21 | var selectOnFirstMatch = false; 22 | 23 | if (selectOnFirstMatchStr != null) { 24 | selectOnFirstMatch = selectOnFirstMatchStr.toLowerCase() == 'true'; 25 | } 26 | 27 | Get.put( 28 | AppPickerController( 29 | type: type, 30 | selectOnFirstMatch: selectOnFirstMatch, 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_title/settings_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_settings/home_settings_controller.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 9 | 10 | class SettingsTitle extends ThemedGetWidget { 11 | const SettingsTitle({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget body(BuildContext context, LeafyTheme theme) { 15 | return PageHeader( 16 | title: L10nProvider.getText(L10n.settingsTitle), 17 | onTapped: controller.onTitleTapped, 18 | largerTitle: true, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/module/startup/startup_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../applications/launcher/app_routes.dart'; 4 | import '../../base/controller/status_controller_base.dart'; 5 | import '../../services/applications/installed_applications_service.dart'; 6 | import '../../services/applications/user_applications_controller.dart'; 7 | 8 | class StartupController extends StatusControllerBase { 9 | late final InstalledApplicationsService _installedApplicationsService; 10 | late final UserApplicationsController _userApplicationsController; 11 | 12 | @override 13 | Future resolveDependencies() async { 14 | _installedApplicationsService = Get.find(); 15 | _userApplicationsController = Get.find(); 16 | } 17 | 18 | @override 19 | Future load() async { 20 | await _installedApplicationsService.ensureInitialized; 21 | await _userApplicationsController.ensureInitialized; 22 | 23 | Get.offAndToNamed(AppRoutes.home); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss/title/home_settings_oss_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_settings/oss/home_settings_oss_controller.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 9 | 10 | class SettingsOssTitle 11 | extends ThemedGetWidget { 12 | const SettingsOssTitle({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | return PageHeader( 17 | title: L10nProvider.getText(L10n.settingsAboutOssTitle), 18 | onTapped: controller.onTitleTapped, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/title/home_settings_widgets_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 9 | 10 | class SettingsWidgetsTitle 11 | extends ThemedGetWidget { 12 | const SettingsWidgetsTitle({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | return PageHeader( 17 | title: L10nProvider.getText(L10n.settingsWidgetsTitle), 18 | onTapped: controller.onTitleTapped, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/take_tutorial/take_tutorial.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/items/values/leafy_section_chevron_value.dart'; 4 | 5 | import '../../../../../resources/localization/l10n.dart'; 6 | import '../../../../../resources/localization/l10n_provider.dart'; 7 | import '../../../../../resources/theme/home_theme.dart'; 8 | import '../../../../../services/applications/user_applications_controller.dart'; 9 | import '../../../../../shared_widget/section/src/items/leafy_section_text_item.dart'; 10 | 11 | class TakeTutorial extends GetWidget { 12 | const TakeTutorial({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return LeafySectionTextItem( 17 | title: L10nProvider.getText(L10n.settingsDoTutorial), 18 | onTap: controller.openTutorial, 19 | value: const LeafySectionChevronValue(), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/home_widgets/home_widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/items/values/leafy_section_chevron_value.dart'; 4 | 5 | import '../../../../../resources/localization/l10n.dart'; 6 | import '../../../../../resources/localization/l10n_provider.dart'; 7 | import '../../../../../resources/theme/home_theme.dart'; 8 | import '../../../../../services/applications/user_applications_controller.dart'; 9 | import '../../../../../shared_widget/section/src/items/leafy_section_text_item.dart'; 10 | 11 | class HomeWidgets extends GetWidget { 12 | const HomeWidgets({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return LeafySectionTextItem( 17 | title: L10nProvider.getText(L10n.settingsHomeWidgetsConfigure), 18 | onTap: controller.openWidgets, 19 | value: const LeafySectionChevronValue(), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/shared_widget/themed_get_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:get/get.dart'; 4 | import '../resources/theme/leafy_theme.dart'; 5 | 6 | abstract class ThemedGetWidget extends GetView { 8 | const ThemedGetWidget({Key? key}) : super(key: key); 9 | 10 | @override 11 | TController get controller => GetInstance().find(tag: tag); 12 | 13 | @override 14 | @nonVirtual 15 | Widget build(BuildContext context) { 16 | final theme = context.dependOnInheritedWidgetOfExactType(); 17 | 18 | assert(theme != null); 19 | 20 | if (theme == null) { 21 | return const ColoredBox( 22 | color: Colors.red, 23 | child: Padding( 24 | padding: EdgeInsets.all(8.0), 25 | child: Text('THEME NOT FOUND!'), 26 | ), 27 | ); 28 | } 29 | 30 | return body(context, theme); 31 | } 32 | 33 | Widget body(BuildContext context, LeafyTheme theme); 34 | } 35 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/folders/widget/title/home_note_folders_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 4 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 5 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 6 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 7 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 8 | 9 | import '../../home_note_folders_controller.dart'; 10 | 11 | class HomeNoteFoldersTitle 12 | extends ThemedGetWidget { 13 | const HomeNoteFoldersTitle({ 14 | Key? key, 15 | }) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return PageHeader( 20 | title: L10nProvider.getText(L10n.leafyNotesFoldersTitle), 21 | hasBackButton: false, 22 | onTapped: controller.onTitleTapped, 23 | largerTitle: true, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/select_default_launcher/select_default_launcher.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/items/values/leafy_section_chevron_value.dart'; 4 | 5 | import '../../../../../resources/localization/l10n.dart'; 6 | import '../../../../../resources/localization/l10n_provider.dart'; 7 | import '../../../../../resources/theme/home_theme.dart'; 8 | import '../../../../../services/applications/user_applications_controller.dart'; 9 | import '../../../../../shared_widget/section/src/items/leafy_section_text_item.dart'; 10 | 11 | class SelectDefaultLauncher extends GetWidget { 12 | const SelectDefaultLauncher({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return LeafySectionTextItem( 17 | title: L10nProvider.getText(L10n.settingsDefaultLauncherChange), 18 | onTap: controller.openLauncherPreferences, 19 | value: const LeafySectionChevronValue(), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 4 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 5 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 6 | import 'package:leafy_launcher/shared_widget/section/src/items/values/leafy_section_text_value.dart'; 7 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 8 | 9 | class LeafySectionEnabledStateItem extends ThemedWidget { 10 | const LeafySectionEnabledStateItem({ 11 | Key? key, 12 | required this.isEnabled, 13 | }) : super(key: key); 14 | 15 | final bool isEnabled; 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | final textKey = 20 | isEnabled ? L10n.settingsSectionEnabled : L10n.settingsSectionDisabled; 21 | 22 | return LeafySectionTextValue( 23 | value: L10nProvider.getText(textKey), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/services/device_locale/device_locale_changed_listener.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | 6 | const _deviceLocaleChangedChannel = EventChannel( 7 | 'com.nivisi.leafy_launcher/deviceLocaleChangedChannel', 8 | ); 9 | 10 | class DeviceLocaleChangedListener { 11 | DeviceLocaleChangedListener() { 12 | init(); 13 | } 14 | 15 | final _controller = StreamController.broadcast(); 16 | Stream get onDeviceLocaleChanged => _controller.stream; 17 | 18 | late StreamSubscription _subscription; 19 | 20 | void init() { 21 | _subscription = _deviceLocaleChangedChannel 22 | .receiveBroadcastStream() 23 | .listen(_onLocaleChanged); 24 | } 25 | 26 | void _onLocaleChanged(event) { 27 | if (event is! String) { 28 | throw Exception('event must be a string'); 29 | } 30 | 31 | final split = event.split('-'); 32 | 33 | final locale = Locale(split[0], split[1]); 34 | 35 | _controller.add(locale); 36 | } 37 | 38 | void dispose() { 39 | _subscription.cancel(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/services/oss_licenses/oss_licenses_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:leafy_launcher/oss_licenses.dart'; 2 | import 'package:leafy_launcher/services/oss_licenses/oss_license.dart'; 3 | import 'package:leafy_launcher/utils/extensions/iterable_extensions.dart'; 4 | 5 | class OssLicensesService { 6 | final List _licenses = []; 7 | Iterable get licenses => _licenses; 8 | 9 | OssLicensesService init() { 10 | for (final license in ossLicenses.entries) { 11 | final name = license.key; 12 | final data = license.value; 13 | 14 | if (data is! Map) { 15 | continue; 16 | } 17 | 18 | final ossLicense = OssLicense( 19 | name: name, 20 | version: data['version'] as String, 21 | description: data['description'] as String?, 22 | license: data['license'] as String, 23 | homepage: data['homepage'] as String?, 24 | ); 25 | 26 | _licenses.add(ossLicense); 27 | } 28 | 29 | return this; 30 | } 31 | 32 | OssLicense? findByName(String name) { 33 | return _licenses.firstWhereOrNull((e) => e.name == name); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/shared_widget/leafy_spacer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../resources/app_constants.dart'; 4 | 5 | enum LeafySpacerType { 6 | horizontal, 7 | vertical, 8 | } 9 | 10 | class LeafySpacer extends StatelessWidget { 11 | const LeafySpacer({ 12 | Key? key, 13 | this.multipler = 1.0, 14 | this.type = LeafySpacerType.vertical, 15 | }) : super(key: key); 16 | 17 | const LeafySpacer.section({Key? key}) : this(key: key, multipler: 5.0); 18 | 19 | const LeafySpacer.horizontal({Key? key, double multipler = 1.0}) 20 | : this( 21 | key: key, 22 | multipler: multipler, 23 | type: LeafySpacerType.horizontal, 24 | ); 25 | 26 | final double multipler; 27 | final LeafySpacerType type; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | switch (type) { 32 | case LeafySpacerType.vertical: 33 | return SizedBox(height: kDefaultPadding * multipler); 34 | case LeafySpacerType.horizontal: 35 | return SizedBox(width: kDefaultPadding * multipler); 36 | default: 37 | throw 'Unknown type'; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/google_search/google_search_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../resources/theme/home_theme.dart'; 4 | import '../../../../resources/theme/leafy_theme.dart'; 5 | import '../../../../shared_widget/themed_widget.dart'; 6 | 7 | class GoogleSearchInput extends ThemedWidget { 8 | const GoogleSearchInput({ 9 | Key? key, 10 | required this.controller, 11 | this.focusNode, 12 | this.onEditingComplete, 13 | }) : super(key: key); 14 | 15 | final TextEditingController controller; 16 | final FocusNode? focusNode; 17 | final void Function(String)? onEditingComplete; 18 | 19 | @override 20 | Widget body(BuildContext context, LeafyTheme theme) { 21 | return TextField( 22 | decoration: InputDecoration( 23 | filled: true, 24 | fillColor: theme.foregroundColor, 25 | prefixIcon: const Icon(Icons.search), 26 | ), 27 | controller: controller, 28 | focusNode: focusNode, 29 | onEditingComplete: onEditingComplete != null 30 | ? () { 31 | onEditingComplete!(controller.text); 32 | } 33 | : null, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/services/app_environment/app_environment.dart: -------------------------------------------------------------------------------- 1 | import 'package:leafy_launcher/utils/app_flavour/app_flavour.dart'; 2 | import 'package:package_info_plus/package_info_plus.dart'; 3 | 4 | class AppEnvironment { 5 | AppEnvironment(this._flavour); 6 | 7 | final AppFlavour _flavour; 8 | 9 | late final String _version; 10 | late final String _build; 11 | late final String _name; 12 | late final String _package; 13 | 14 | AppFlavour get flavour => _flavour; 15 | bool get isDev => _flavour == AppFlavour.dev; 16 | bool get isProd => _flavour == AppFlavour.prod; 17 | 18 | String get version => _version; 19 | String get build => _build; 20 | String get name => _name; 21 | String get package => _package; 22 | 23 | String get github => 'https://github.com/nivisi/LeafyLauncher'; 24 | String get email => 'leafylauncher@gmail.com'; 25 | String get telegramChat => 'https://t.me/+MJJJZbkBNpdhMzdi'; 26 | 27 | Future init() async { 28 | final packageInfo = await PackageInfo.fromPlatform(); 29 | 30 | _name = packageInfo.appName; 31 | _package = packageInfo.packageName; 32 | _version = packageInfo.version; 33 | _build = packageInfo.buildNumber; 34 | 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/shared_widget/list/dismissible_delete_background.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home/utils/gesture_processer.dart'; 3 | import 'package:leafy_launcher/resources/app_constants.dart'; 4 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 5 | 6 | import '../themed_widget.dart'; 7 | 8 | class DismissibleDeleteBackground 9 | extends ThemedWidget { 10 | const DismissibleDeleteBackground(this.direction); 11 | 12 | final Direction direction; 13 | 14 | @override 15 | Widget body(BuildContext context, LeafyTheme theme) { 16 | final alignment = direction == Direction.right 17 | ? Alignment.centerRight 18 | : Alignment.centerLeft; 19 | 20 | final padding = direction == Direction.right 21 | ? const EdgeInsets.only(right: kDefaultPadding) 22 | : const EdgeInsets.only(left: kDefaultPadding); 23 | 24 | return Container( 25 | padding: padding, 26 | color: theme.deleteColor, 27 | child: Align( 28 | alignment: alignment, 29 | child: Icon( 30 | Icons.delete_forever_rounded, 31 | color: theme.foregroundColor, 32 | ), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lib/utils/enum/leafy_theme_style.dart: -------------------------------------------------------------------------------- 1 | import '../../resources/localization/l10n.dart'; 2 | import '../../resources/localization/l10n_provider.dart'; 3 | 4 | enum LeafyThemeStyle { 5 | light, 6 | dark, 7 | } 8 | 9 | extension LeafyThemeStyleExtensions on LeafyThemeStyle { 10 | String localize() { 11 | switch (this) { 12 | case LeafyThemeStyle.light: 13 | return L10nProvider.getText(L10n.themeStyleLight); 14 | case LeafyThemeStyle.dark: 15 | return L10nProvider.getText(L10n.themeStyleDark); 16 | default: 17 | throw Exception('Unknown type'); 18 | } 19 | } 20 | } 21 | 22 | const String _light = 'Light'; 23 | const String _dark = 'Dark'; 24 | 25 | String stringifyLeafyThemeStyle(LeafyThemeStyle style) { 26 | switch (style) { 27 | case LeafyThemeStyle.light: 28 | return _light; 29 | case LeafyThemeStyle.dark: 30 | return _dark; 31 | 32 | default: 33 | throw Exception('Unknown type'); 34 | } 35 | } 36 | 37 | LeafyThemeStyle leafyThemeStyleFromString(String str) { 38 | switch (str) { 39 | case _light: 40 | return LeafyThemeStyle.light; 41 | case _dark: 42 | return LeafyThemeStyle.dark; 43 | default: 44 | throw Exception('Unknown!'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/module/app_picker/widget/app_picker_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../resources/theme/home_theme.dart'; 4 | import '../../../resources/theme/leafy_theme.dart'; 5 | import '../../../services/applications/application.dart'; 6 | import '../../../shared_widget/themed_widget.dart'; 7 | import '../../../shared_widget/touchable_text_button.dart'; 8 | 9 | class AppPickerButton extends ThemedWidget { 10 | const AppPickerButton({ 11 | Key? key, 12 | required this.application, 13 | required this.onTapped, 14 | this.onLongPress, 15 | }) : super(key: key); 16 | 17 | final Application application; 18 | final void Function(Application app) onTapped; 19 | final void Function(Application app)? onLongPress; 20 | 21 | @override 22 | Widget body(BuildContext context, LeafyTheme theme) { 23 | return TouchableTextButton( 24 | text: application.name, 25 | color: theme.foregroundColor, 26 | pressedColor: theme.foregroundPressedColor, 27 | style: theme.bodyText1, 28 | onTap: () { 29 | onTapped(application); 30 | }, 31 | onLongPress: onLongPress == null 32 | ? null 33 | : () { 34 | onLongPress!(application); 35 | }, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/assets/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/lib/resources/app_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const kButtonPressForwardAnimationDuration = Duration(milliseconds: 15); 4 | const kButtonPressReverseAnimationDuration = Duration(milliseconds: 100); 5 | const kDefaultAnimationDuration = Duration(milliseconds: 250); 6 | const kDefaultRouteTransitionDuration = Duration(milliseconds: 100); 7 | const kAppRefetchDuration = Duration(minutes: 10); 8 | 9 | const kDefaultPadding = 10.0; 10 | 11 | const kHomeHorizontalPadding = kDefaultPadding * 2.5; 12 | const kHomeVerticalPadding = kDefaultPadding * 4.0; 13 | const kStatusBarPadding = 20.0; 14 | const kHomeAppsSpacingMultipler = 2.0; 15 | const kHomeHorizontalSwipeIconSize = 55.0; 16 | const kHomeCornerButtonEdgeInsets = EdgeInsets.only( 17 | left: kDefaultPadding * 1.3, 18 | right: kDefaultPadding * 1.3, 19 | bottom: kDefaultPadding, 20 | ); 21 | const kHomeSearchOffsetMultipler = 50.0; 22 | 23 | const kBarrierColor = Color(0x66000000); 24 | 25 | const kUserAppListTopPadding = EdgeInsets.only(top: kDefaultPadding * 7.5); 26 | 27 | const kSettingsIcon = Icons.settings; 28 | const kCameraAppIcon = Icons.camera_alt; 29 | const kPhoneAppIcon = Icons.phone; 30 | const kMessagesAppIcon = Icons.message; 31 | 32 | const kDefaultAnimationCurve = Curves.fastOutSlowIn; 33 | const kDefaultBorderRadius = BorderRadius.all(Radius.circular(10.0)); 34 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/folders/home_note_folders_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/base/page/page_base.dart'; 3 | import 'package:leafy_launcher/base/page/status_page_base.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | 8 | import 'home_note_folders_controller.dart'; 9 | import 'widget/fab/home_note_folders_fab.dart'; 10 | import 'widget/list/home_note_folders_list.dart'; 11 | import 'widget/title/home_note_folders_title.dart'; 12 | 13 | class HomeNoteFoldersPage 14 | extends StatusPageBase { 15 | const HomeNoteFoldersPage(); 16 | 17 | static const horizontalPadding = kDefaultPadding; 18 | 19 | @override 20 | OnWillPopData get onWillPopData => OnWillPopData(controller.back); 21 | 22 | @override 23 | Widget ready(BuildContext context, LeafyTheme theme) { 24 | return Column( 25 | crossAxisAlignment: CrossAxisAlignment.stretch, 26 | children: const [ 27 | HomeNoteFoldersTitle(), 28 | Expanded(child: HomeNoteFoldersList()), 29 | ], 30 | ); 31 | } 32 | 33 | @override 34 | Widget fab(BuildContext context, LeafyTheme theme) { 35 | return const HomeNoteFoldersFab(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/android/app/src/main/kotlin/com/nivisi/leafy_launcher/installed_packages/LauncherAppsCallback.kt: -------------------------------------------------------------------------------- 1 | package com.nivisi.leafy_launcher.installed_packages 2 | 3 | import android.content.pm.LauncherApps 4 | import android.os.UserHandle 5 | import com.nivisi.leafy_launcher.broadcast_receivers.AppChangeReceiver 6 | 7 | class LauncherAppsCallback : LauncherApps.Callback() { 8 | override fun onPackageAdded(packageName: String, user: UserHandle) { 9 | AppChangeReceiver.handle( 10 | "android.intent.action.PACKAGE_ADDED", 11 | packageName, 12 | false 13 | ) 14 | } 15 | 16 | override fun onPackageChanged(packageName: String, user: UserHandle) { 17 | // Not implemented 18 | } 19 | 20 | override fun onPackageRemoved(packageName: String, user: UserHandle) { 21 | AppChangeReceiver.handle( 22 | "android.intent.action.PACKAGE_REMOVED", 23 | packageName, 24 | false 25 | ) 26 | } 27 | 28 | 29 | override fun onPackagesAvailable( 30 | packageNames: Array?, 31 | user: UserHandle?, 32 | replacing: Boolean 33 | ) { 34 | // Not implemented 35 | } 36 | 37 | override fun onPackagesUnavailable( 38 | packageNames: Array?, 39 | user: UserHandle?, 40 | replacing: Boolean 41 | ) { 42 | // Not implemented 43 | } 44 | } -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/items/leafy_section_text_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 4 | 5 | import 'leafy_section_custom_item.dart'; 6 | 7 | class LeafySectionTextItem 8 | extends ThemedWidget { 9 | const LeafySectionTextItem({ 10 | Key? key, 11 | required this.title, 12 | this.subtitle, 13 | this.leading, 14 | this.value, 15 | this.onTap, 16 | this.onLongPress, 17 | }) : super(key: key); 18 | 19 | final String title; 20 | final String? subtitle; 21 | final Widget? leading; 22 | final Widget? value; 23 | final VoidCallback? onTap; 24 | final VoidCallback? onLongPress; 25 | 26 | @override 27 | Widget body(BuildContext context, LeafyTheme theme) { 28 | return LeafySectionCustomItem( 29 | title: Text( 30 | title, 31 | style: theme.bodyText5, 32 | maxLines: 1, 33 | overflow: TextOverflow.ellipsis, 34 | ), 35 | subtitle: subtitle != null 36 | ? Text( 37 | subtitle!, 38 | style: theme.bodyText4.copyWith(color: theme.textInfoColor), 39 | ) 40 | : null, 41 | onTap: onTap, 42 | onLongPress: onLongPress, 43 | leading: leading, 44 | value: value, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/theme/leafy_section_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/app_constants.dart'; 3 | 4 | class LeafySectionTheme extends InheritedWidget { 5 | const LeafySectionTheme({ 6 | Key? key, 7 | required Widget child, 8 | this.leadingAlwaysTakesSpace = false, 9 | required this.leadingWidth, 10 | this.itemHorizontalPadding = kDefaultPadding, 11 | this.itemVerticalPadding = kDefaultPadding * 1.5, 12 | this.backgroundColor = Colors.transparent, 13 | this.sectionBorderRadius = BorderRadius.zero, 14 | }) : super( 15 | key: key, 16 | child: child, 17 | ); 18 | 19 | final bool leadingAlwaysTakesSpace; 20 | final double leadingWidth; 21 | final double itemHorizontalPadding; 22 | final double itemVerticalPadding; 23 | final Color backgroundColor; 24 | final BorderRadius sectionBorderRadius; 25 | 26 | @override 27 | bool updateShouldNotify(covariant LeafySectionTheme oldWidget) { 28 | return leadingAlwaysTakesSpace != oldWidget.leadingAlwaysTakesSpace; 29 | } 30 | 31 | LeafySectionTheme? of(BuildContext context) { 32 | return context.dependOnInheritedWidgetOfExactType(); 33 | } 34 | } 35 | 36 | extension LeafySectionFinder on BuildContext { 37 | LeafySectionTheme? get leafySectionTheme => 38 | dependOnInheritedWidgetOfExactType(); 39 | } 40 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss/home_settings_oss_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/applications/launcher/app_routes.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | import 'package:leafy_launcher/services/oss_licenses/oss_license.dart'; 6 | import 'package:leafy_launcher/services/oss_licenses/oss_licenses_service.dart'; 7 | 8 | import '../../../base/controller/status_controller_base.dart'; 9 | 10 | class HomeSettingsOssController extends StatusControllerBase { 11 | late final OssLicensesService _ossLicensesService; 12 | late final ScrollController scrollController; 13 | 14 | Iterable get licenses => _ossLicensesService.licenses; 15 | 16 | @override 17 | Future resolveDependencies() { 18 | _ossLicensesService = Get.find(); 19 | 20 | return super.resolveDependencies(); 21 | } 22 | 23 | @override 24 | Future load() async { 25 | await super.load(); 26 | 27 | scrollController = ScrollController(); 28 | } 29 | 30 | void openLibrary(OssLicense license) { 31 | AppRoutes.toOssLicense(name: license.name); 32 | } 33 | 34 | void onTitleTapped() { 35 | if (scrollController.hasClients) { 36 | scrollController.animateTo( 37 | .0, 38 | duration: kDefaultAnimationDuration, 39 | curve: kDefaultAnimationCurve, 40 | ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/folder/model/folder_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class FolderModel extends Equatable { 4 | const FolderModel({ 5 | required this.id, 6 | required this.title, 7 | required this.lastEditedAt, 8 | required this.createdAt, 9 | required this.isDefault, 10 | required this.isArchived, 11 | required this.isPinned, 12 | }); 13 | 14 | final String id; 15 | final String title; 16 | final DateTime lastEditedAt; 17 | final DateTime createdAt; 18 | final bool isDefault; 19 | final bool isArchived; 20 | final bool isPinned; 21 | 22 | FolderModel copyWith({ 23 | String? id, 24 | String? title, 25 | DateTime? lastEditedAt, 26 | DateTime? createdAt, 27 | bool? isDefault, 28 | bool? isArchived, 29 | bool? isPinned, 30 | }) { 31 | return FolderModel( 32 | id: id ?? this.id, 33 | title: title ?? this.title, 34 | lastEditedAt: lastEditedAt ?? this.lastEditedAt, 35 | createdAt: createdAt ?? this.createdAt, 36 | isDefault: isDefault ?? this.isDefault, 37 | isArchived: isArchived ?? this.isArchived, 38 | isPinned: isDefault ?? this.isPinned, 39 | ); 40 | } 41 | 42 | @override 43 | List get props => [ 44 | id, 45 | title, 46 | lastEditedAt, 47 | createdAt, 48 | isDefault, 49 | isArchived, 50 | isPinned, 51 | ]; 52 | } 53 | -------------------------------------------------------------------------------- /src/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | avoid_classes_with_only_static_methods: false 6 | await_only_futures: true 7 | 8 | # Temporary 9 | public_member_api_docs: false 10 | type_annotate_public_apis: false 11 | 12 | # This looks more organized. 13 | sort_constructors_first: true 14 | 15 | # This looks more organized. 16 | sort_pub_dependencies: true 17 | 18 | # Single qoutes looks prettier and more compact. 19 | prefer_single_quotes: true 20 | 21 | lines_longer_than_80_chars: true 22 | 23 | # Make variables "var" only when really needed. 24 | prefer_final_locals: true 25 | 26 | # This doesn't make the code more readable. 27 | # It forces us not to use 28 | # final variable = 5; 29 | # But to use 30 | # final int variable = 5; 31 | always_specify_types: false 32 | 33 | # Will be fixed 34 | always_use_package_imports: false 35 | 36 | analyzer: 37 | errors: 38 | prefer_const_constructors: warning 39 | missing_return: error 40 | missing_required_param: error 41 | directives_ordering: warning 42 | unnecessary_new: warning 43 | unnecessary_this: warning 44 | unnecessary_const: warning 45 | todo: ignore 46 | no_logic_in_create_state: ignore 47 | invariant_booleans: warning 48 | avoid_classes_with_only_static_members: ignore 49 | exclude: 50 | [lib/**.g.dart, lib/oss_licenses.dart] -------------------------------------------------------------------------------- /src/lib/module/home_settings/home_settings_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/applications/launcher/app_routes.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | 6 | import '../../base/controller/status_controller_base.dart'; 7 | import '../../services/applications/application.dart'; 8 | import '../../services/applications/user_applications_controller.dart'; 9 | 10 | class HomeSettingsController extends StatusControllerBase { 11 | late final UserApplicationsController _userApplicationsController; 12 | 13 | Application? get left => _userApplicationsController.swipeLeftApp; 14 | Application? get right => _userApplicationsController.swipeRightApp; 15 | 16 | late final ScrollController scrollController; 17 | 18 | @override 19 | Future resolveDependencies() async { 20 | _userApplicationsController = Get.find(); 21 | } 22 | 23 | @override 24 | Future load() async { 25 | await super.load(); 26 | 27 | scrollController = ScrollController(); 28 | } 29 | 30 | void onTitleTapped() { 31 | if (scrollController.hasClients) { 32 | scrollController.animateTo( 33 | .0, 34 | duration: kDefaultAnimationDuration, 35 | curve: kDefaultAnimationCurve, 36 | ); 37 | } 38 | } 39 | 40 | Future? openAbout() { 41 | return Get.toNamed(AppRoutes.settingsAbout); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/notes/widget/list/home_notes_empty_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/app_constants.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | import 'package:leafy_launcher/shared_widget/leafy_spacer.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 9 | 10 | class HomeNotesEmptyWidget extends ThemedWidget { 11 | const HomeNotesEmptyWidget({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget body(BuildContext context, LeafyTheme theme) { 15 | return Padding( 16 | padding: const EdgeInsets.all(kDefaultPadding * 2.0), 17 | child: Column( 18 | children: [ 19 | Text( 20 | L10nProvider.getText(L10n.leafyNotesNotesEmptyStateMessageEmoji), 21 | style: theme.bodyText1, 22 | textAlign: TextAlign.center, 23 | ), 24 | const LeafySpacer(), 25 | Text( 26 | L10nProvider.getText(L10n.leafyNotesNotesEmptyStateMessage), 27 | style: theme.bodyText5.copyWith(color: theme.textInfoColor), 28 | textAlign: TextAlign.center, 29 | ), 30 | ], 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/android/app/src/main/kotlin/com/nivisi/leafy_launcher/NotesActivity.kt: -------------------------------------------------------------------------------- 1 | package com.nivisi.leafy_launcher 2 | 3 | import android.app.ActivityManager 4 | import android.os.Bundle 5 | import android.os.PersistableBundle 6 | import io.flutter.embedding.engine.FlutterEngine 7 | 8 | 9 | class NotesActivity: LeafyActivityBase() { 10 | override fun onResume() { 11 | super.onResume() 12 | 13 | overridePendingTransition(R.anim.app_launch_fade_in_long, R.anim.app_launch_fade_out_long) 14 | } 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | val title: String = resources.getString(R.string.leafy_notes_title) 20 | 21 | setTaskDescription(ActivityManager.TaskDescription(title)) 22 | } 23 | 24 | override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { 25 | super.onCreate(savedInstanceState, persistentState) 26 | 27 | val title: String = resources.getString(R.string.leafy_notes_title) 28 | 29 | setTaskDescription(ActivityManager.TaskDescription(title)) 30 | } 31 | 32 | override fun appName(): String { 33 | return "leafyNotes" 34 | } 35 | 36 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 37 | super.configureFlutterEngine(flutterEngine) 38 | 39 | self = this 40 | } 41 | 42 | companion object { 43 | var self: NotesActivity? = null 44 | } 45 | } -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/leafy_notes_database.dart: -------------------------------------------------------------------------------- 1 | library leafy_notes_database; 2 | 3 | import 'dart:async'; 4 | 5 | // ignore: depend_on_referenced_packages 6 | import 'package:get/get.dart'; 7 | 8 | import 'src/leafy_notes_db.dart'; 9 | import 'src/models.dart'; 10 | import 'src/models/folder/model/folder_converter.dart'; 11 | import 'src/models/folder/repository/folder_repository_impl.dart'; 12 | import 'src/models/note/repository/note_repository_impl.dart'; 13 | import 'src/repositories/repositories.dart'; 14 | 15 | export 'src/models.dart'; 16 | export 'src/one_to_manys.dart'; 17 | export 'src/repositories/repositories.dart'; 18 | 19 | class LeafyNotesDatabaseLibrary { 20 | static final Completer _completer = Completer(); 21 | 22 | static Future get ensureInitialized => _completer.future; 23 | 24 | static late final FolderModel defaultFolder; 25 | 26 | static Future _prepareDatabase(FolderRepositoryImpl folderRepository) async { 27 | final defaultFolderDb = 28 | await LeafyNotesDb.folderDao.createDefaultFolderIfNeeded(); 29 | 30 | folderRepository.defaultFolder = folderModelFromDb(defaultFolderDb); 31 | } 32 | 33 | static void initialize(GetInterface get) { 34 | get.put(NoteRepositoryImpl()); 35 | final foldersRepository = get.put(FolderRepositoryImpl()) 36 | as FolderRepositoryImpl; 37 | 38 | _prepareDatabase(foldersRepository) 39 | .whenComplete(() => _completer.complete()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/services/platform_methods/platform_methods_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | import '../../utils/log/logable_mixin.dart'; 4 | 5 | const _channel = MethodChannel('com.nivisi.leafy_launcher/common'); 6 | 7 | const _methodOpenPhoneApp = 'openPhoneApp'; 8 | const _methodOpenCameraApp = 'openCameraApp'; 9 | const _methodOpenMessagesApp = 'openMessagesApp'; 10 | const _methodOpenClockApp = 'openClockApp'; 11 | const _methodOpenLauncherPreferences = 'openLauncherPreferences'; 12 | const _methodDeleteApp = 'deleteApp'; 13 | const _methodOpenLeafyNotes = 'openLeafyNotes'; 14 | 15 | class PlatformMethodsService with LogableMixin { 16 | Future openPhoneApp() async { 17 | return _channel.invokeMethod(_methodOpenPhoneApp); 18 | } 19 | 20 | Future openCameraApp() { 21 | return _channel.invokeMethod(_methodOpenCameraApp); 22 | } 23 | 24 | Future openMessagesApp() { 25 | return _channel.invokeMethod(_methodOpenMessagesApp); 26 | } 27 | 28 | Future openClockApp() { 29 | return _channel.invokeMethod(_methodOpenClockApp); 30 | } 31 | 32 | Future openLauncherPreferences() { 33 | return _channel.invokeMethod(_methodOpenLauncherPreferences); 34 | } 35 | 36 | Future deleteApp(String packageName) { 37 | return _channel.invokeMethod( 38 | _methodDeleteApp, 39 | {'packageName': packageName}, 40 | ); 41 | } 42 | 43 | Future openLeafyNotes() { 44 | return _channel.invokeMethod(_methodOpenLeafyNotes); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/time_progress/time_progress_type_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home/widget/home_widgets/time_progress/time_progress.dart'; 5 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 7 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 8 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 9 | import 'package:leafy_launcher/shared_widget/section/leafy_section_lib.dart'; 10 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 11 | 12 | class TimeProgressTypeState extends ThemedGetWidget { 13 | const TimeProgressTypeState({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget body(BuildContext context, LeafyTheme theme) { 17 | return LeafySectionTextItem( 18 | title: L10nProvider.getText(L10n.settingsTimeProgressType), 19 | onTap: controller.nextTimeProgressType, 20 | value: GetBuilder( 21 | id: HomeController.timeProgressTypeBuilderKey, 22 | builder: (controller) { 23 | return LeafySectionTextValue( 24 | value: localizeTimeProgressType(controller.timeProgressType), 25 | ); 26 | }, 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/swipe_apps/swipe_to_left_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 4 | 5 | import '../../../../../resources/localization/l10n.dart'; 6 | import '../../../../../resources/localization/l10n_provider.dart'; 7 | import '../../../../../resources/theme/home_theme.dart'; 8 | import '../../../../../services/applications/user_applications_controller.dart'; 9 | import '../../../../../shared_widget/section/src/items/values/leafy_section_text_value.dart'; 10 | import '../../../../../utils/enum/user_selected_app_type.dart'; 11 | 12 | class SwipeToLeftApp extends GetWidget { 13 | const SwipeToLeftApp({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return LeafySectionTextItem( 18 | title: L10nProvider.getText(L10n.settingsToLeftApp), 19 | onTap: () => controller.selectApp(UserSelectedAppType.left), 20 | value: GetBuilder( 21 | id: controller.getBuilderId(UserSelectedAppType.left), 22 | init: controller, 23 | builder: (controller) { 24 | final app = controller.getApp(UserSelectedAppType.left); 25 | 26 | return LeafySectionTextValue( 27 | value: app?.name ?? L10nProvider.getText(L10n.homeSelectApp), 28 | ); 29 | }, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/notes/widget/list/home_note_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:leafy_launcher/database/leafy_notes_db/leafy_notes_database.dart'; 4 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 5 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 6 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 7 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 8 | 9 | import '../../home_notes_page.dart'; 10 | 11 | class HomeNoteSectionItem extends ThemedWidget { 12 | const HomeNoteSectionItem({ 13 | Key? key, 14 | required this.note, 15 | this.onTap, 16 | this.onLongPress, 17 | }) : super(key: key); 18 | 19 | static late final _todayFormat = DateFormat.Hm(); 20 | static late final _format = DateFormat.yMMMMEEEEd(); 21 | 22 | final NoteModel note; 23 | final OnNoteSelected? onTap; 24 | final OnNoteSelected? onLongPress; 25 | 26 | @override 27 | Widget body(BuildContext context, LeafyTheme theme) { 28 | return LeafySectionTextItem( 29 | title: note.title ?? note.firstLine ?? '', 30 | subtitle: note.lastEditedAt.day == DateTime.now().day 31 | ? _todayFormat.format(note.lastEditedAt) 32 | : _format.format(note.lastEditedAt), 33 | onTap: onTap == null ? null : () => onTap!(note), 34 | onLongPress: onLongPress == null ? null : () => onLongPress!(note), 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widget/settings_body/swipe_apps/swipe_to_right_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 4 | 5 | import '../../../../../resources/localization/l10n.dart'; 6 | import '../../../../../resources/localization/l10n_provider.dart'; 7 | import '../../../../../resources/theme/home_theme.dart'; 8 | import '../../../../../services/applications/user_applications_controller.dart'; 9 | import '../../../../../shared_widget/section/src/items/values/leafy_section_text_value.dart'; 10 | import '../../../../../utils/enum/user_selected_app_type.dart'; 11 | 12 | class SwipeToRightApp extends GetWidget { 13 | const SwipeToRightApp({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return LeafySectionTextItem( 18 | title: L10nProvider.getText(L10n.settingsToRightApp), 19 | onTap: () => controller.selectApp(UserSelectedAppType.right), 20 | value: GetBuilder( 21 | id: controller.getBuilderId(UserSelectedAppType.right), 22 | init: controller, 23 | builder: (controller) { 24 | final app = controller.getApp(UserSelectedAppType.right); 25 | 26 | return LeafySectionTextValue( 27 | value: app?.name ?? L10nProvider.getText(L10n.homeSelectApp), 28 | ); 29 | }, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/home_widgets/time_progress/time_progress_type.dart: -------------------------------------------------------------------------------- 1 | part of 'time_progress.dart'; 2 | 3 | const _kDay = 'day'; 4 | const _kWeek = 'week'; 5 | const _kYear = 'year'; 6 | 7 | enum TimeProgressType { 8 | day, 9 | week, 10 | year, 11 | } 12 | 13 | TimeProgressType timeProgressTypeFromString(String str) { 14 | switch (str) { 15 | case _kDay: 16 | return TimeProgressType.day; 17 | case _kWeek: 18 | return TimeProgressType.week; 19 | case _kYear: 20 | return TimeProgressType.year; 21 | default: 22 | throw Exception('Unknown VibrationPreferencesType'); 23 | } 24 | } 25 | 26 | String timeProgressTypeToString(TimeProgressType type) { 27 | switch (type) { 28 | case TimeProgressType.day: 29 | return _kDay; 30 | case TimeProgressType.week: 31 | return _kWeek; 32 | case TimeProgressType.year: 33 | return _kYear; 34 | default: 35 | throw Exception('Unknown VibrationPreferencesType'); 36 | } 37 | } 38 | 39 | String localizeTimeProgressType(TimeProgressType preferences) { 40 | late String l10nKey; 41 | 42 | switch (preferences) { 43 | case TimeProgressType.day: 44 | l10nKey = L10n.timeProgressDay; 45 | break; 46 | case TimeProgressType.week: 47 | l10nKey = L10n.timeProgressWeek; 48 | break; 49 | case TimeProgressType.year: 50 | l10nKey = L10n.timeProgressYear; 51 | break; 52 | default: 53 | throw Exception('Unknown TimeProgressTypeType'); 54 | } 55 | 56 | return L10nProvider.getText(l10nKey); 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/corner_button/_configured_icon_button.dart: -------------------------------------------------------------------------------- 1 | part of 'corner_button.dart'; 2 | 3 | class _ConfiguredIconButton extends ThemedWidget { 4 | const _ConfiguredIconButton({ 5 | Key? key, 6 | required this.type, 7 | required this.onPressed, 8 | this.onLongPressed, 9 | this.margin, 10 | }) : super(key: key); 11 | 12 | static const _size = 48.0; 13 | 14 | final CornerButtonType type; 15 | final VoidCallback onPressed; 16 | final VoidCallback? onLongPressed; 17 | final EdgeInsets? margin; 18 | 19 | IconData _iconDataFromCornerButtonType(CornerButtonType type) { 20 | switch (type) { 21 | case CornerButtonType.phone: 22 | return Icons.phone; 23 | case CornerButtonType.messages: 24 | return Icons.message; 25 | case CornerButtonType.camera: 26 | return Icons.camera_alt; 27 | default: 28 | throw Exception('Unknown CornerButtonType'); 29 | } 30 | } 31 | 32 | @override 33 | Widget body(BuildContext context, LeafyTheme theme) { 34 | return Container( 35 | width: _size, 36 | height: _size, 37 | margin: margin, 38 | decoration: const BoxDecoration( 39 | shape: BoxShape.circle, 40 | ), 41 | clipBehavior: Clip.antiAliasWithSaveLayer, 42 | child: LeafyTextButton( 43 | onPressed: onPressed, 44 | onLongPressed: onLongPressed, 45 | child: Icon( 46 | _iconDataFromCornerButtonType(type), 47 | color: theme.foregroundColor, 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/clock/clock_enabled_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 5 | import 'package:leafy_launcher/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 7 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 8 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 9 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 10 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 11 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 12 | 13 | class ClockEnabledState 14 | extends ThemedGetWidget { 15 | const ClockEnabledState({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return LeafySectionTextItem( 20 | title: L10nProvider.getText(L10n.settingsSectionIsEnabled), 21 | onTap: controller.updateIsClockVisible, 22 | value: GetBuilder( 23 | id: HomeController.clockBuilderKey, 24 | builder: (controller) { 25 | return LeafySectionEnabledStateItem( 26 | isEnabled: controller.isClockVisible, 27 | ); 28 | }, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/horizontal_swipe_app_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../resources/theme/home_theme.dart'; 5 | import '../../../resources/theme/leafy_theme.dart'; 6 | import '../../../services/applications/user_applications_controller.dart'; 7 | import '../../../shared_widget/themed_get_widget.dart'; 8 | import '../../../utils/enum/user_selected_app_type.dart'; 9 | 10 | class HorizontalSwipeAppIcon 11 | extends ThemedGetWidget { 12 | const HorizontalSwipeAppIcon({ 13 | Key? key, 14 | required this.appType, 15 | }) : super(key: key); 16 | 17 | final UserSelectedAppType appType; 18 | 19 | @override 20 | Widget body(BuildContext context, LeafyTheme theme) { 21 | return Container( 22 | clipBehavior: Clip.antiAliasWithSaveLayer, 23 | decoration: const BoxDecoration(shape: BoxShape.circle), 24 | margin: const EdgeInsets.all(5.0), 25 | child: GetBuilder( 26 | init: controller, 27 | id: controller.getBuilderId(appType), 28 | builder: (controller) { 29 | final icon = appType == UserSelectedAppType.left 30 | ? controller.leftAppIcon 31 | : controller.rightAppIcon; 32 | 33 | return icon == null 34 | ? Icon( 35 | Icons.settings, 36 | color: theme.foregroundColor, 37 | size: 40, 38 | ) 39 | : Image.memory(icon); 40 | }, 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/calendar/calendar_enabled_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 5 | import 'package:leafy_launcher/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 7 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 8 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 9 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 10 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 11 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 12 | 13 | class CalendarEnabledState 14 | extends ThemedGetWidget { 15 | const CalendarEnabledState({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return LeafySectionTextItem( 20 | title: L10nProvider.getText(L10n.settingsSectionIsEnabled), 21 | onTap: controller.updateIsCalendarVisible, 22 | value: GetBuilder( 23 | id: HomeController.calendarBuilderKey, 24 | builder: (controller) { 25 | return LeafySectionEnabledStateItem( 26 | isEnabled: controller.isCalendarVisible, 27 | ); 28 | }, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/user_app_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../resources/localization/l10n.dart'; 4 | import '../../../resources/localization/l10n_provider.dart'; 5 | import '../../../resources/theme/home_theme.dart'; 6 | import '../../../resources/theme/leafy_theme.dart'; 7 | import '../../../services/applications/application.dart'; 8 | import '../../../shared_widget/themed_widget.dart'; 9 | import '../../../shared_widget/touchable_text_button.dart'; 10 | 11 | class UserAppButton extends ThemedWidget { 12 | const UserAppButton({ 13 | Key? key, 14 | required Application? application, 15 | required void Function(Application? application) onTapped, 16 | required void Function() onLongPress, 17 | TextStyle? textStyle, 18 | }) : _application = application, 19 | _onTapped = onTapped, 20 | _onLongPress = onLongPress, 21 | _textStyle = textStyle, 22 | super(key: key); 23 | 24 | final Application? _application; 25 | final void Function(Application? application) _onTapped; 26 | final void Function() _onLongPress; 27 | final TextStyle? _textStyle; 28 | 29 | @override 30 | Widget body(BuildContext context, LeafyTheme theme) { 31 | return TouchableTextButton( 32 | text: _application?.name ?? L10nProvider.getText(L10n.homeSelectApp), 33 | style: _textStyle ?? theme.bodyText1, 34 | color: theme.foregroundColor, 35 | pressedColor: theme.foregroundPressedColor, 36 | onTap: () { 37 | _onTapped(_application); 38 | }, 39 | onLongPress: _onLongPress, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leafy Launcher 🌱 ![app version][app-version-img] 2 | 3 | An Android Launcher built with Flutter. 4 | 5 | [![Leafy CI 🌱][ci-badge-url]][ci-url] [![Leafy Release 🌳][release-ci-badge-url]][release-ci-url] [![CodeFactor][code-factor--badge-url]][code-factor-app-url] 6 | 7 | --- 8 | 9 | > **Note** 10 | > 11 | > More info & screenshots to come soon... 12 | 13 | --- 14 | 15 | ## Contact us 16 | 17 | - Join the Telegram chat, [@leafy_launcher](https://t.me/+Y4P6JS4dqXFkMjMy); 18 | - Send us an email to [leafylauncher@gmail.com](mailto:leafylauncher@gmail.com). 19 | 20 | ## Contribution Guidelines 21 | 22 | Any ideas, feature requests, bug reports and PRs are welcome! To keep the codebase and the repo clean and structured, we created [contribution guidelines](https://github.com/nivisi/LeafyLauncher/wiki/Contribution-Guidelines). Take a glance at it before doing anything! 23 | 24 | 25 | [code-factor--badge-url]: https://www.codefactor.io/repository/github/nivisi/leafylauncher/badge?s=12760533a8fde6261b394c1023a0e4e8e3ca6a7a 26 | [code-factor-app-url]: https://www.codefactor.io/repository/github/nivisi/leafylauncher 27 | [app-version-img]: https://img.shields.io/badge/version-1.2.4_beta-green 28 | 29 | [ci-badge-url]: https://github.com/nivisi/LeafyLauncher/actions/workflows/leafy_ci.yml/badge.svg?branch=develop 30 | [ci-url]: https://github.com/nivisi/LeafyLauncher/actions/workflows/leafy_ci.yml 31 | 32 | [release-ci-badge-url]: https://github.com/nivisi/LeafyLauncher/actions/workflows/leafy_release_ci.yml/badge.svg 33 | [release-ci-url]: https://github.com/nivisi/LeafyLauncher/actions/workflows/leafy_release_ci.yml 34 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/time_progress/time_progress_enabled_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 5 | import 'package:leafy_launcher/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 7 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 8 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 9 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 10 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 11 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 12 | 13 | class TimeProgressEnabledState 14 | extends ThemedGetWidget { 15 | const TimeProgressEnabledState({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return LeafySectionTextItem( 20 | title: L10nProvider.getText(L10n.settingsSectionIsEnabled), 21 | onTap: controller.updateIsTimeProgressVisible, 22 | value: GetBuilder( 23 | id: HomeController.timeProgressBuilderKey, 24 | builder: (controller) { 25 | return LeafySectionEnabledStateItem( 26 | isEnabled: controller.isTimeProgressVisible, 27 | ); 28 | }, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/resources/settings/vibration_preferences.dart: -------------------------------------------------------------------------------- 1 | part of 'leafy_settings.dart'; 2 | 3 | const _kAlways = 'always'; 4 | const _kOnLaunch = 'onLaunch'; 5 | const _kNever = 'never'; 6 | 7 | enum VibrationPreferences { 8 | always, 9 | onLaunch, 10 | never, 11 | } 12 | 13 | VibrationPreferences _vibrationPreferencesFromString(String str) { 14 | switch (str) { 15 | case _kAlways: 16 | return VibrationPreferences.always; 17 | case _kOnLaunch: 18 | return VibrationPreferences.onLaunch; 19 | case _kNever: 20 | return VibrationPreferences.never; 21 | default: 22 | throw Exception('Unknown VibrationPreferencesType'); 23 | } 24 | } 25 | 26 | String _vibrationPreferencesToString( 27 | VibrationPreferences preferences, 28 | ) { 29 | switch (preferences) { 30 | case VibrationPreferences.always: 31 | return _kAlways; 32 | case VibrationPreferences.onLaunch: 33 | return _kOnLaunch; 34 | case VibrationPreferences.never: 35 | return _kNever; 36 | default: 37 | throw Exception('Unknown VibrationPreferencesType'); 38 | } 39 | } 40 | 41 | String localizeVibrationPreferences(VibrationPreferences preferences) { 42 | switch (preferences) { 43 | case VibrationPreferences.always: 44 | return L10nProvider.getText(L10n.vibrationPreferencesAlways); 45 | case VibrationPreferences.onLaunch: 46 | return L10nProvider.getText(L10n.vibrationPreferencesOnLaunch); 47 | case VibrationPreferences.never: 48 | return L10nProvider.getText(L10n.vibrationPreferencesNever); 49 | default: 50 | throw Exception('Unknown VibrationPreferencesType'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/utils/dialogs/confirm/actions_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/resources/app_constants.dart'; 4 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 5 | import 'package:leafy_launcher/utils/dialogs/leafy_dialog.dart'; 6 | 7 | class ActionsDialog 8 | extends StatelessWidget { 9 | const ActionsDialog._({ 10 | required this.title, 11 | this.message, 12 | required this.options, 13 | this.fallbackResult, 14 | }); 15 | 16 | final String title; 17 | final String? message; 18 | final Iterable> options; 19 | final TResult? fallbackResult; 20 | 21 | static Future show({ 22 | required String title, 23 | String? message, 24 | required Iterable> options, 25 | TResult? fallbackResult, 26 | bool barrierDismissible = true, 27 | }) async { 28 | final result = await Get.dialog( 29 | ActionsDialog._( 30 | title: title, 31 | message: message, 32 | options: options, 33 | fallbackResult: fallbackResult, 34 | ), 35 | barrierColor: kBarrierColor, 36 | barrierDismissible: barrierDismissible, 37 | ); 38 | 39 | return result ?? fallbackResult; 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return LeafyDialog( 45 | title: Text(title), 46 | message: message != null ? Text(message!) : null, 47 | options: options, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/corner_apps/left_corner_app_enabled_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 5 | import 'package:leafy_launcher/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 7 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 8 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 9 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 10 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 11 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 12 | 13 | class LeftCornerAppEnabledState 14 | extends ThemedGetWidget { 15 | const LeftCornerAppEnabledState({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return LeafySectionTextItem( 20 | title: L10nProvider.getText(L10n.settingsSectionIsEnabled), 21 | onTap: controller.updateIsLeftCornerAppVisible, 22 | value: GetBuilder( 23 | id: HomeController.leftCornerButtonBuilderKey, 24 | builder: (controller) { 25 | return LeafySectionEnabledStateItem( 26 | isEnabled: controller.isLeftCornerButtonVisible, 27 | ); 28 | }, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/body/corner_apps/right_corner_app_enabled_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/module/home_settings/widgets/home_settings_widgets_controller.dart'; 5 | import 'package:leafy_launcher/module/home_settings/widgets/widget/leafy_section_enabled_state_item.dart'; 6 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 7 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 8 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 9 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 10 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 11 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 12 | 13 | class RightCornerAppEnabledState 14 | extends ThemedGetWidget { 15 | const RightCornerAppEnabledState({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget body(BuildContext context, LeafyTheme theme) { 19 | return LeafySectionTextItem( 20 | title: L10nProvider.getText(L10n.settingsSectionIsEnabled), 21 | onTap: controller.updateIsRightCornerAppVisible, 22 | value: GetBuilder( 23 | id: HomeController.rightCornerButtonBuilderKey, 24 | builder: (controller) { 25 | return LeafySectionEnabledStateItem( 26 | isEnabled: controller.isRightCornerButtonVisible, 27 | ); 28 | }, 29 | ), 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/google_search/google_search_results.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../../resources/app_constants.dart'; 4 | import '../../../../resources/theme/home_theme.dart'; 5 | import '../../../../resources/theme/leafy_theme.dart'; 6 | import '../../../../shared_widget/list/list_builder.dart'; 7 | import '../../../../shared_widget/themed_widget.dart'; 8 | 9 | class GoogleSearchResults extends ThemedWidget { 10 | const GoogleSearchResults({ 11 | Key? key, 12 | required this.results, 13 | required this.onSuggestionPicked, 14 | }) : super(key: key); 15 | 16 | final Iterable results; 17 | final void Function(String) onSuggestionPicked; 18 | 19 | @override 20 | Widget body(BuildContext context, LeafyTheme theme) { 21 | if (results.isEmpty) { 22 | return Padding( 23 | padding: const EdgeInsets.all(kDefaultPadding), 24 | child: Text( 25 | 'Nothing Found!', 26 | style: TextStyle(color: theme.foregroundColor), 27 | ), 28 | ); 29 | } 30 | 31 | return ListBuilder( 32 | builder: (item) { 33 | return TextButton( 34 | onPressed: () { 35 | onSuggestionPicked(item); 36 | }, 37 | child: Align( 38 | alignment: Alignment.centerLeft, 39 | child: Text( 40 | item, 41 | style: TextStyle( 42 | color: theme.foregroundColor, 43 | ), 44 | textAlign: TextAlign.start, 45 | ), 46 | ), 47 | ); 48 | }, 49 | items: results, 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/notes/home_notes_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/base/page/status_page_base.dart'; 3 | import 'package:leafy_launcher/database/leafy_notes_db/leafy_notes_database.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | 8 | import 'home_notes_controller.dart'; 9 | import 'widget/list/home_notes_list.dart'; 10 | import 'widget/title/home_notes_title.dart'; 11 | 12 | typedef OnNoteSelected = void Function(NoteModel note); 13 | 14 | class HomeNotesPage extends StatusPageBase { 15 | const HomeNotesPage(); 16 | 17 | static const horizontalPadding = kDefaultPadding; 18 | 19 | @override 20 | Widget ready(BuildContext context, LeafyTheme theme) { 21 | return Column( 22 | crossAxisAlignment: CrossAxisAlignment.stretch, 23 | children: const [ 24 | HomeNotesTitle(), 25 | Expanded(child: HomeNotesList()), 26 | ], 27 | ); 28 | } 29 | 30 | @override 31 | Widget fab(BuildContext context, LeafyTheme theme) { 32 | return Stack( 33 | children: [ 34 | Positioned( 35 | right: kDefaultPadding, 36 | bottom: kDefaultPadding, 37 | child: FloatingActionButton( 38 | foregroundColor: theme.backgroundColor, 39 | backgroundColor: theme.leafyColor, 40 | onPressed: controller.onFabPressed, 41 | child: const Icon(Icons.add), 42 | ), 43 | ), 44 | ], 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/notes/widget/title/home_notes_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/database/leafy_notes_db/leafy_notes_database.dart'; 3 | import 'package:leafy_launcher/module/home_notes/notes/notes/home_notes_controller.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 5 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 6 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 7 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 8 | import 'package:leafy_launcher/shared_widget/page/page_header.dart'; 9 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 10 | 11 | class HomeNotesTitle extends ThemedGetWidget { 12 | const HomeNotesTitle({ 13 | Key? key, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget body(BuildContext context, LeafyTheme theme) { 18 | return PageHeader.builder( 19 | titleBuilder: (context, textStyle) { 20 | return StreamBuilder( 21 | stream: controller.folderStream, 22 | builder: (context, snapshot) { 23 | if (!snapshot.hasData || snapshot.hasError) { 24 | return const SizedBox(); 25 | } 26 | 27 | final folder = snapshot.data!; 28 | 29 | final text = folder.isDefault 30 | ? L10nProvider.getText(L10n.defaultFolderTitle) 31 | : folder.title; 32 | 33 | return Text(text, style: textStyle); 34 | }, 35 | ); 36 | }, 37 | onTapped: controller.onTitleTapped, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/base/page/page_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../resources/theme/leafy_theme.dart'; 5 | import '../controller/controller_base.dart'; 6 | 7 | class OnWillPopData { 8 | OnWillPopData(this.onWillPop); 9 | 10 | Future Function() onWillPop; 11 | } 12 | 13 | abstract class PageBase extends GetView { 15 | const PageBase({Key? key}) : super(key: key); 16 | 17 | @protected 18 | bool get resizeToAvoidBottomInset => true; 19 | 20 | @protected 21 | bool get safeArea => true; 22 | 23 | @protected 24 | OnWillPopData? get onWillPopData => OnWillPopData(controller.back); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | Widget widget = LeafyThemeState( 29 | builder: (context, theme) { 30 | Widget widget = Scaffold( 31 | resizeToAvoidBottomInset: resizeToAvoidBottomInset, 32 | backgroundColor: theme.backgroundColor, 33 | body: pageBody(context, theme), 34 | floatingActionButton: fab(context, theme), 35 | ); 36 | 37 | if (safeArea) { 38 | widget = SafeArea(child: widget); 39 | } 40 | 41 | return widget; 42 | }, 43 | ); 44 | 45 | if (onWillPopData != null) { 46 | widget = WillPopScope( 47 | onWillPop: onWillPopData!.onWillPop, 48 | child: widget, 49 | ); 50 | } 51 | 52 | return widget; 53 | } 54 | 55 | Widget pageBody(BuildContext context, LeafyTheme theme); 56 | 57 | Widget? fab(BuildContext context, LeafyTheme theme) { 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/model/note_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | class NoteModel extends Equatable { 4 | const NoteModel({ 5 | required this.id, 6 | required this.title, 7 | this.firstLine = '', 8 | this.data = '', 9 | required this.lastEditedAt, 10 | required this.createdAt, 11 | required this.folderId, 12 | required this.isArchived, 13 | required this.isPinned, 14 | }); 15 | 16 | final String id; 17 | final String? title; 18 | final String? firstLine; 19 | final String? data; 20 | final DateTime lastEditedAt; 21 | final DateTime createdAt; 22 | final String folderId; 23 | final bool isArchived; 24 | final bool isPinned; 25 | 26 | NoteModel copyWith({ 27 | String? id, 28 | String? title, 29 | String? firstLine, 30 | String? data, 31 | DateTime? lastEditedAt, 32 | DateTime? createdAt, 33 | String? folderId, 34 | bool? isArchived, 35 | bool? isPinned, 36 | }) { 37 | return NoteModel( 38 | id: id ?? this.id, 39 | title: title ?? this.title, 40 | firstLine: firstLine ?? this.firstLine, 41 | data: data ?? this.data, 42 | lastEditedAt: lastEditedAt ?? this.lastEditedAt, 43 | createdAt: createdAt ?? this.createdAt, 44 | folderId: folderId ?? this.folderId, 45 | isArchived: isArchived ?? this.isArchived, 46 | isPinned: isPinned ?? this.isPinned, 47 | ); 48 | } 49 | 50 | @override 51 | List get props => [ 52 | id, 53 | title, 54 | firstLine, 55 | data, 56 | lastEditedAt, 57 | createdAt, 58 | isArchived, 59 | isPinned, 60 | ]; 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/services/file_system/file_system.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path_provider/path_provider.dart'; 4 | 5 | class FileSystem { 6 | FileSystem._(); 7 | 8 | static FileSystem? _instance; 9 | 10 | late final Directory _appDocumentsDirectory; 11 | late final Directory _loggingDirectory; 12 | late final Directory _leafyNotesDocumentsDirectory; 13 | 14 | Directory get appDocumentsDirectory => _appDocumentsDirectory; 15 | 16 | Directory get loggingDirectory { 17 | if (!_loggingDirectory.existsSync()) { 18 | _loggingDirectory.createSync(); 19 | } 20 | return _loggingDirectory; 21 | } 22 | 23 | Directory get leafyNotesDocumentsDirectory { 24 | if (!_leafyNotesDocumentsDirectory.existsSync()) { 25 | _leafyNotesDocumentsDirectory.createSync(); 26 | } 27 | return _leafyNotesDocumentsDirectory; 28 | } 29 | 30 | void _initLoggingDirectory() { 31 | final loggingDirectoryPath = '${_appDocumentsDirectory.path}/Logging'; 32 | _loggingDirectory = Directory(loggingDirectoryPath); 33 | } 34 | 35 | void _initLeafyNotesDirectory() { 36 | final leafyNotesDirectoryPath = '${_appDocumentsDirectory.path}/LeafyNotes'; 37 | _leafyNotesDocumentsDirectory = Directory(leafyNotesDirectoryPath); 38 | } 39 | 40 | Future _init() async { 41 | _appDocumentsDirectory = await getApplicationDocumentsDirectory(); 42 | 43 | _initLoggingDirectory(); 44 | _initLeafyNotesDirectory(); 45 | } 46 | 47 | static Future init() async { 48 | if (_instance != null) { 49 | return _instance!; 50 | } 51 | 52 | final fileSystem = FileSystem._(); 53 | await fileSystem._init(); 54 | 55 | return _instance = fileSystem; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/utils/log/simple_log_printer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:intl/intl.dart'; 4 | 5 | import 'package:logger/logger.dart'; 6 | 7 | class SimpleLogPrinter extends LogPrinter { 8 | SimpleLogPrinter({dynamic forObject}) : _prefix = forObject?.toString(); 9 | 10 | final _formatted = DateFormat('HH:mm:ss'); 11 | final String? _prefix; 12 | final prettyPrinter = PrettyPrinter(methodCount: 0); 13 | 14 | static final levelPrefixes = { 15 | Level.verbose: '[V]', 16 | Level.debug: '[D]', 17 | Level.info: '[I]', 18 | Level.warning: '[W]', 19 | Level.error: '[E]', 20 | Level.wtf: '[WTF]', 21 | }; 22 | 23 | @override 24 | List log(LogEvent event) { 25 | final messageStr = _stringifyMessage(event.message); 26 | final errorStr = event.error != null ? ' ERROR: ${event.error}' : ''; 27 | final timeStr = _formatted.format(DateTime.now()); 28 | final list = ['$timeStr ${_labelFor(event.level)} $messageStr$errorStr']; 29 | if (event.stackTrace == null) { 30 | return list; 31 | } 32 | final stackTrace = prettyPrinter.formatStackTrace(event.stackTrace, 0); 33 | if (stackTrace != null) { 34 | list.add(stackTrace); 35 | } 36 | return list; 37 | } 38 | 39 | String _labelFor(Level level) { 40 | return levelPrefixes[level]!; 41 | } 42 | 43 | String _stringifyMessage(dynamic message) { 44 | String msg; 45 | if (message is Map || message is Iterable) { 46 | const encoder = JsonEncoder.withIndent(null); 47 | msg = encoder.convert(message); 48 | } else { 49 | msg = message.toString(); 50 | } 51 | if (_prefix == null) { 52 | return msg; 53 | } 54 | return '$_prefix : $msg'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/icons/gmail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/lib/applications/launcher/app_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | import '../../module/app_picker/app_picker_controller_base.dart'; 4 | import '../../services/applications/application.dart'; 5 | import '../../utils/enum/user_selected_app_type.dart'; 6 | 7 | class AppRoutes { 8 | static const appPicker = '/appPicker'; 9 | static const appPickerSignature = '/appPicker/:type'; 10 | 11 | static const home = '/home'; 12 | 13 | static const settings = '/settings'; 14 | static const settingsWidgets = '/settings/widgets'; 15 | static const settingsAbout = '/settings/about'; 16 | static const settingsOss = '/settings/about/oss'; 17 | static const settingsOssLicense = '/settings/about/oss/:name'; 18 | static const tutorial = '/tutorial'; 19 | 20 | static Future? toHome({bool off = false}) { 21 | return off ? Get.offNamed(home) : Get.toNamed(home); 22 | } 23 | 24 | static Future? toAppPicker({ 25 | UserSelectedAppType? type, 26 | bool returnOnFirstMatch = false, 27 | }) async { 28 | final returnOnFirstMatchStr = returnOnFirstMatch ? 'true' : 'false'; 29 | 30 | const paramName = AppPickerControllerBase.selectOnFirstMatchParameter; 31 | 32 | if (type == null) { 33 | return Get.toNamed( 34 | '''$appPicker?$paramName=$returnOnFirstMatchStr''', 35 | ); 36 | } 37 | 38 | final str = stringifyUserSelectedAppType(type); 39 | 40 | final res = await Get.toNamed( 41 | '$appPicker/$str?$paramName=$returnOnFirstMatchStr', 42 | ); 43 | 44 | return res as Application?; 45 | } 46 | 47 | static Future? toOssLicense({required String name}) { 48 | final route = settingsOssLicense.replaceAll(':name', name); 49 | 50 | return Get.toNamed(route); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/base/page/status_page_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | import '../../../resources/app_constants.dart'; 5 | import '../../resources/theme/leafy_theme.dart'; 6 | import '../../shared_widget/loader.dart'; 7 | import '../controller/status_controller_base.dart'; 8 | import 'page_base.dart'; 9 | 10 | abstract class StatusPageBase extends PageBase { 12 | const StatusPageBase(); 13 | 14 | Widget loading(BuildContext context, LeafyTheme theme) { 15 | return const Center(child: Loader()); 16 | } 17 | 18 | Widget error(BuildContext context, LeafyTheme theme, ControllerError? error) { 19 | if (error != null) { 20 | return Center(child: Text(error.toString())); 21 | } 22 | 23 | return Center( 24 | child: Text('Something went wrong ... \n ${error?.toString() ?? ''}'), 25 | ); 26 | } 27 | 28 | Widget ready(BuildContext context, LeafyTheme theme); 29 | 30 | @override 31 | Widget pageBody(BuildContext context, LeafyTheme theme) { 32 | return AnimatedSwitcher( 33 | duration: kDefaultAnimationDuration, 34 | child: GetBuilder( 35 | key: ValueKey(controller.status), 36 | id: StatusControllerBase.statusGetKey, 37 | init: controller, 38 | builder: (controller) { 39 | if (controller.isReady) { 40 | return ready(context, theme); 41 | } else if (controller.isLoading) { 42 | return loading(context, theme); 43 | } else if (controller.isError) { 44 | return error(context, theme, controller.error); 45 | } else { 46 | throw Exception('Page had an unknown status'); 47 | } 48 | }, 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/folders/widget/list/home_note_folder_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/database/leafy_notes_db/leafy_notes_database.dart'; 3 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 5 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 6 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 7 | import 'package:leafy_launcher/shared_widget/section/src/items/leafy_section_text_item.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 9 | 10 | typedef OnFolderSelected = void Function(FolderModel note); 11 | 12 | class HomeNoteFolderSectionItem extends ThemedWidget { 13 | const HomeNoteFolderSectionItem({ 14 | Key? key, 15 | required this.folderWithNotes, 16 | this.onTap, 17 | this.onLongPress, 18 | }) : super(key: key); 19 | 20 | final FolderWithNotes folderWithNotes; 21 | final OnFolderSelected? onTap; 22 | final OnFolderSelected? onLongPress; 23 | 24 | @override 25 | Widget body(BuildContext context, LeafyTheme theme) { 26 | final folder = folderWithNotes.folder; 27 | final notes = folderWithNotes.notes; 28 | 29 | return LeafySectionTextItem( 30 | title: folder.isDefault 31 | ? L10nProvider.getText(L10n.defaultFolderTitle) 32 | : folder.title, 33 | leading: Icon(Icons.folder_outlined, color: theme.foregroundColor), 34 | onTap: onTap == null ? null : () => onTap!(folder), 35 | onLongPress: onLongPress == null ? null : () => onLongPress!(folder), 36 | value: Text( 37 | notes.length.toString(), 38 | style: theme.bodyText4.copyWith(color: theme.textInfoColor), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/module/home_notes/notes/note/widget/body/home_note_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_notes/notes/note/home_note_controller.dart'; 3 | import 'package:leafy_launcher/resources/app_constants.dart'; 4 | import 'package:leafy_launcher/resources/localization/l10n.dart'; 5 | import 'package:leafy_launcher/resources/localization/l10n_provider.dart'; 6 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 7 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 8 | import 'package:leafy_launcher/shared_widget/themed_get_widget.dart'; 9 | 10 | class HomeNoteBody extends ThemedGetWidget { 11 | const HomeNoteBody({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget body(BuildContext context, LeafyTheme theme) { 15 | return Expanded( 16 | child: TextField( 17 | expands: true, 18 | maxLines: null, 19 | controller: controller.bodyEditingController, 20 | style: theme.bodyText3, 21 | autofocus: controller.shouldAutofocusBody, 22 | textCapitalization: TextCapitalization.sentences, 23 | decoration: InputDecoration( 24 | border: InputBorder.none, 25 | focusedBorder: InputBorder.none, 26 | enabledBorder: InputBorder.none, 27 | errorBorder: InputBorder.none, 28 | disabledBorder: InputBorder.none, 29 | hintStyle: theme.bodyText3.copyWith( 30 | fontWeight: FontWeight.w500, 31 | color: theme.textInfoColor, 32 | ), 33 | hintText: L10nProvider.getText(L10n.leafyNotesNoteBodyPlaceholder), 34 | contentPadding: const EdgeInsets.only( 35 | left: kDefaultPadding, 36 | right: kDefaultPadding, 37 | bottom: kDefaultPadding, 38 | ), 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss/body/settings_oss_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/module/home_settings/oss/home_settings_oss_controller.dart'; 3 | import 'package:leafy_launcher/shared_widget/leafy_spacer.dart'; 4 | import 'package:leafy_launcher/shared_widget/section/leafy_section_lib.dart'; 5 | import 'package:leafy_launcher/shared_widget/section/src/list/leafy_section_list.dart'; 6 | 7 | import '../../../../resources/theme/home_theme.dart'; 8 | import '../../../../resources/theme/leafy_theme.dart'; 9 | import '../../../../shared_widget/themed_get_widget.dart'; 10 | 11 | class SettingsOssBody 12 | extends ThemedGetWidget { 13 | const SettingsOssBody({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget body(BuildContext context, LeafyTheme theme) { 17 | return LeafySectionList( 18 | scrollController: controller.scrollController, 19 | sections: [ 20 | LeafySection( 21 | children: controller.licenses.map( 22 | (license) { 23 | return LeafySectionTextItem( 24 | title: license.name, 25 | onTap: () => controller.openLibrary(license), 26 | value: Row( 27 | children: [ 28 | const LeafySpacer.horizontal(), 29 | Text( 30 | license.version, 31 | style: theme.bodyText5.copyWith( 32 | color: theme.textInfoColor, 33 | ), 34 | ), 35 | const LeafySpacer.horizontal(), 36 | const LeafySectionChevronValue(), 37 | ], 38 | ), 39 | ); 40 | }, 41 | ), 42 | ), 43 | ], 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/body/settings_about_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:leafy_launcher/resources/assets/leafy_icons.dart'; 4 | import 'package:leafy_launcher/shared_widget/section/leafy_section_lib.dart'; 5 | import 'package:leafy_launcher/shared_widget/section/src/list/leafy_section_list.dart'; 6 | 7 | import '../../../../resources/localization/l10n.dart'; 8 | import '../../../../resources/localization/l10n_provider.dart'; 9 | import '../../../../resources/theme/home_theme.dart'; 10 | import '../../../../resources/theme/leafy_theme.dart'; 11 | import '../../../../shared_widget/themed_get_widget.dart'; 12 | import '../home_settings_about_controller.dart'; 13 | 14 | part 'info/github.dart'; 15 | part 'info/gmail.dart'; 16 | part 'info/telegram.dart'; 17 | part 'oss/oss.dart'; 18 | 19 | class SettingsAboutBody 20 | extends ThemedGetWidget { 21 | const SettingsAboutBody({Key? key}) : super(key: key); 22 | 23 | @override 24 | Widget body(BuildContext context, LeafyTheme theme) { 25 | return LeafySectionList( 26 | scrollController: controller.scrollController, 27 | sections: [ 28 | LeafySection( 29 | footer: L10nProvider.getText(L10n.settingsAboutInfo), 30 | children: const [ 31 | _GitHub(), 32 | _Gmail(), 33 | _Telegram(), 34 | ], 35 | ), 36 | LeafySection( 37 | header: L10nProvider.getText(L10n.settingsAboutOssHeader), 38 | footer: L10nProvider.getText(L10n.settingsAboutOssFooter), 39 | children: const [ 40 | _Oss(), 41 | ], 42 | ), 43 | LeafySection( 44 | footer: controller.leafyVersion, 45 | children: const [], 46 | ), 47 | ], 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/android/app/src/main/kotlin/com/nivisi/leafy_launcher/LeafyActivityBase.kt: -------------------------------------------------------------------------------- 1 | package com.nivisi.leafy_launcher 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | import io.flutter.embedding.engine.FlutterEngine 5 | import io.flutter.plugin.common.EventChannel 6 | import io.flutter.plugin.common.MethodChannel 7 | 8 | 9 | abstract class LeafyActivityBase: FlutterActivity() { 10 | abstract fun appName() :String 11 | 12 | private var deviceLocaleChangedEventChannel: EventChannel? = null 13 | private var deviceLocaleChangedEventStreamHandler: StreamHandlerParams = StreamHandlerParams() 14 | 15 | private fun registerDeviceLocaleChangedEventChannel(flutterEngine: FlutterEngine) { 16 | deviceLocaleChangedEventChannel = EventChannel( 17 | flutterEngine.dartExecutor.binaryMessenger, 18 | deviceLocaleChangedChannel 19 | ) 20 | deviceLocaleChangedEventChannel!!.setStreamHandler(deviceLocaleChangedEventStreamHandler) 21 | } 22 | 23 | fun dispatchDeviceLocaleChangedEvent(locale: String) { 24 | deviceLocaleChangedEventStreamHandler.dispatch(locale) 25 | } 26 | 27 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 28 | super.configureFlutterEngine(flutterEngine) 29 | 30 | registerDeviceLocaleChangedEventChannel(flutterEngine) 31 | 32 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.nivisi.leafy_launcher/app").setMethodCallHandler { call, result -> 33 | when (call.method) { 34 | "app" -> { 35 | result.success(appName()) 36 | return@setMethodCallHandler 37 | } 38 | else -> result.notImplemented() 39 | } 40 | } 41 | } 42 | 43 | companion object { 44 | private const val deviceLocaleChangedChannel = "com.nivisi.leafy_launcher/deviceLocaleChangedChannel" 45 | } 46 | } -------------------------------------------------------------------------------- /src/lib/module/intro/tutorial/domain/slide_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:leafy_launcher/resources/app_constants.dart'; 4 | 5 | enum ScenarioEventType { 6 | init, 7 | forward, 8 | repeat, 9 | end, 10 | } 11 | 12 | enum SlideControllerState { 13 | onPause, 14 | playing, 15 | } 16 | 17 | class SlideController { 18 | final StreamController _eventController = 19 | StreamController.broadcast(); 20 | final StreamController _onDoneController = 21 | StreamController.broadcast(); 22 | 23 | bool _isDone = false; 24 | 25 | SlideControllerState state = SlideControllerState.onPause; 26 | 27 | Stream get widgetEvents => _eventController.stream; 28 | Stream get onDone => _eventController.stream; 29 | 30 | void init() { 31 | state = SlideControllerState.onPause; 32 | 33 | _eventController.add(ScenarioEventType.init); 34 | } 35 | 36 | void forward() { 37 | state = SlideControllerState.playing; 38 | 39 | _eventController.add(ScenarioEventType.forward); 40 | } 41 | 42 | void repeat() { 43 | state = SlideControllerState.playing; 44 | 45 | _eventController.add(ScenarioEventType.repeat); 46 | } 47 | 48 | Future end() { 49 | state = SlideControllerState.onPause; 50 | 51 | _eventController.add(ScenarioEventType.end); 52 | 53 | return Future.delayed(kDefaultAnimationDuration); 54 | } 55 | 56 | void done() { 57 | if (state == SlideControllerState.onPause) { 58 | _isDone = true; 59 | 60 | return; 61 | } 62 | 63 | state = SlideControllerState.onPause; 64 | 65 | if (_isDone) { 66 | return; 67 | } 68 | 69 | _isDone = true; 70 | _onDoneController.add(null); 71 | } 72 | 73 | void dispose() { 74 | state = SlideControllerState.onPause; 75 | 76 | _onDoneController.close(); 77 | _eventController.close(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/home_widgets/home_date.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:get/get.dart'; 5 | import 'package:intl/intl.dart'; 6 | import 'package:leafy_launcher/module/home/home_controller.dart'; 7 | 8 | import '../../../../resources/theme/home_theme.dart'; 9 | import '../../../../shared_widget/themed_state.dart'; 10 | import '../../../../shared_widget/touchable_text_button.dart'; 11 | 12 | class HomeDate extends StatefulWidget { 13 | const HomeDate({Key? key}) : super(key: key); 14 | 15 | @override 16 | _HomeDateState createState() => _HomeDateState(); 17 | } 18 | 19 | class _HomeDateState extends ThemedState { 20 | final DateFormat _format = DateFormat('dd/MM/y'); 21 | 22 | late final Timer _timer; 23 | late DateTime _time; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | 29 | _time = DateTime.now(); 30 | 31 | _timer = Timer.periodic( 32 | const Duration(seconds: 1), 33 | (timer) { 34 | setState(() { 35 | _time = DateTime.now(); 36 | }); 37 | }, 38 | ); 39 | } 40 | 41 | @override 42 | Widget body(BuildContext context, HomeTheme theme) { 43 | return GetBuilder( 44 | id: HomeController.calendarBuilderKey, 45 | init: Get.find(), 46 | builder: (controller) { 47 | return Visibility( 48 | visible: controller.isCalendarVisible, 49 | child: TouchableTextButton( 50 | color: theme.foregroundColor, 51 | pressedColor: theme.foregroundPressedColor, 52 | text: _format.format(_time), 53 | style: theme.bodyText4, 54 | onTap: Get.find().openCalendar, 55 | ), 56 | ); 57 | }, 58 | ); 59 | } 60 | 61 | @override 62 | void dispose() { 63 | _timer.cancel(); 64 | 65 | super.dispose(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/module/home/widget/curved_background.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../utils/gesture_processer.dart'; 4 | 5 | class CurvePainter extends CustomPainter { 6 | const CurvePainter({ 7 | required this.color, 8 | required this.position, 9 | required this.direction, 10 | }); 11 | 12 | static const firstXStop = 10.0; 13 | static const secondXStop = 35.0; 14 | static const thirdXStop = 86.0; 15 | 16 | static const firstYStop = .34; 17 | static const secondYStop = .43; 18 | static const thirdYStop = .45; 19 | static const fourthYStop = .5; 20 | 21 | final Color color; 22 | final double position; 23 | final Direction direction; 24 | 25 | @override 26 | void paint(Canvas canvas, Size size) { 27 | final paint = Paint() 28 | ..color = color.withOpacity(position / 3.0) 29 | ..style = PaintingStyle.fill; 30 | 31 | final path = Path(); 32 | 33 | final startPosition = direction == Direction.right ? .0 : size.width; 34 | final multipler = direction == Direction.right ? 1.0 : -1.0; 35 | 36 | path 37 | ..moveTo(startPosition, size.height * firstYStop) 38 | ..quadraticBezierTo( 39 | startPosition + (firstXStop * multipler), 40 | size.height * secondYStop, 41 | startPosition + (position * secondXStop * multipler), 42 | size.height * thirdYStop, 43 | ) 44 | ..quadraticBezierTo( 45 | startPosition + (position * thirdXStop * multipler), 46 | size.height * fourthYStop, 47 | startPosition + (position * secondXStop * multipler), 48 | size.height * (1.0 - thirdYStop), 49 | ) 50 | ..quadraticBezierTo( 51 | startPosition + (firstXStop * multipler), 52 | size.height * (1.0 - secondYStop), 53 | startPosition, 54 | size.height * (1.0 - firstYStop), 55 | ); 56 | 57 | canvas.drawPath(path, paint); 58 | } 59 | 60 | @override 61 | bool shouldRepaint(CustomPainter oldDelegate) { 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/widgets/home_settings_widgets_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/module/home/home_controller.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | 6 | import '../../../base/controller/status_controller_base.dart'; 7 | 8 | class HomeSettingsWidgetsController extends StatusControllerBase { 9 | late final HomeController _homeController; 10 | late final ScrollController scrollController; 11 | 12 | @override 13 | Future resolveDependencies() { 14 | _homeController = Get.find(); 15 | 16 | return super.resolveDependencies(); 17 | } 18 | 19 | @override 20 | Future load() async { 21 | await super.load(); 22 | 23 | scrollController = ScrollController(); 24 | } 25 | 26 | void updateIsTimeProgressVisible() { 27 | final newValue = !_homeController.isTimeProgressVisible; 28 | 29 | _homeController.setIsTimeProgressVisible(value: newValue); 30 | } 31 | 32 | void updateIsCalendarVisible() { 33 | final newValue = !_homeController.isCalendarVisible; 34 | 35 | _homeController.setIsCalendarVisible(value: newValue); 36 | } 37 | 38 | void updateIsClockVisible() { 39 | final newValue = !_homeController.isClockVisible; 40 | 41 | _homeController.setIsClockVisible(value: newValue); 42 | } 43 | 44 | void updateIsLeftCornerAppVisible() { 45 | final newValue = !_homeController.isLeftCornerButtonVisible; 46 | 47 | _homeController.setIsLeftCornerAppVisible(value: newValue); 48 | } 49 | 50 | void updateIsRightCornerAppVisible() { 51 | final newValue = !_homeController.isRightCornerButtonVisible; 52 | 53 | _homeController.setIsRightCornerAppVisible(value: newValue); 54 | } 55 | 56 | void onTitleTapped() { 57 | if (scrollController.hasClients) { 58 | scrollController.animateTo( 59 | .0, 60 | duration: kDefaultAnimationDuration, 61 | curve: kDefaultAnimationCurve, 62 | ); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/module/app_picker/widget/app_picker_fade.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/app_constants.dart'; 3 | import 'package:leafy_launcher/resources/theme/home_theme.dart'; 4 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 5 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 6 | 7 | class AppPickerFade extends ThemedWidget { 8 | const AppPickerFade({Key? key, required this.child}) : super(key: key); 9 | 10 | final Widget child; 11 | 12 | @override 13 | Widget body(BuildContext context, LeafyTheme theme) { 14 | return Stack( 15 | children: [ 16 | Padding( 17 | padding: const EdgeInsets.only(top: 2.0), 18 | child: child, 19 | ), 20 | Align( 21 | alignment: Alignment.topCenter, 22 | child: IgnorePointer( 23 | child: Container( 24 | height: kDefaultPadding * 2.5, 25 | decoration: BoxDecoration( 26 | gradient: LinearGradient( 27 | colors: [ 28 | theme.backgroundColor, 29 | Colors.transparent, 30 | ], 31 | begin: Alignment.topCenter, 32 | end: Alignment.bottomCenter, 33 | ), 34 | ), 35 | ), 36 | ), 37 | ), 38 | Align( 39 | alignment: Alignment.bottomCenter, 40 | child: IgnorePointer( 41 | child: Container( 42 | height: kDefaultPadding * 4.0, 43 | decoration: BoxDecoration( 44 | gradient: LinearGradient( 45 | colors: [ 46 | theme.backgroundColor, 47 | Colors.transparent, 48 | ], 49 | begin: Alignment.bottomCenter, 50 | end: Alignment.topCenter, 51 | ), 52 | ), 53 | ), 54 | ), 55 | ), 56 | ], 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/database/leafy_notes_db/src/models/note/repository/note_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:leafy_launcher/database/leafy_notes_db/leafy_notes_database.dart'; 2 | import 'package:moor_flutter/moor_flutter.dart'; 3 | import 'package:uuid/uuid.dart'; 4 | 5 | import '../../../leafy_notes_db.dart'; 6 | import '../model/note_converter.dart'; 7 | 8 | class NoteRepositoryImpl implements NoteRepository { 9 | final _dao = LeafyNotesDb.noteDao; 10 | 11 | @override 12 | Future create(FolderModel folder) async { 13 | final companion = NotesCompanion( 14 | id: Value(const Uuid().v1()), 15 | folderId: Value(folder.id), 16 | createdAt: Value(DateTime.now().toUtc()), 17 | lastEditedAt: Value(DateTime.now().toUtc()), 18 | ); 19 | 20 | final note = await _dao.insertNote(companion); 21 | 22 | return noteModelFromDb(note); 23 | } 24 | 25 | @override 26 | Future update(NoteModel model) async { 27 | final title = model.title == '' ? null : model.title; 28 | final data = model.data == '' ? null : model.data; 29 | final firstLine = model.firstLine == '' ? null : model.firstLine; 30 | 31 | final updated = NoteModel( 32 | id: model.id, 33 | title: title, 34 | data: data, 35 | firstLine: firstLine, 36 | lastEditedAt: DateTime.now().toUtc(), 37 | createdAt: model.createdAt, 38 | folderId: model.folderId, 39 | isArchived: model.isArchived, 40 | isPinned: model.isPinned, 41 | ); 42 | 43 | final note = noteModelToDb(updated); 44 | 45 | await _dao.replaceNote(note); 46 | } 47 | 48 | @override 49 | Future delete(NoteModel model) async { 50 | final note = noteModelToDb(model); 51 | 52 | await _dao.deleteNote(note); 53 | } 54 | 55 | @override 56 | Stream watchAllNotesOfFolder(String id) { 57 | return _dao.watchAllNotesOfFolder(id); 58 | } 59 | 60 | @override 61 | Future getById(String id) async { 62 | final note = await _dao.getById(id); 63 | 64 | return noteModelFromDb(note); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/about/home_settings_about_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/applications/launcher/app_routes.dart'; 4 | import 'package:leafy_launcher/resources/app_constants.dart'; 5 | import 'package:leafy_launcher/services/app_environment/app_environment.dart'; 6 | 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | import '../../../base/controller/status_controller_base.dart'; 10 | 11 | class HomeSettingsAboutController extends StatusControllerBase { 12 | late final AppEnvironment _appEnvironment; 13 | late final ScrollController scrollController; 14 | 15 | late final String leafyVersion; 16 | 17 | @override 18 | Future resolveDependencies() { 19 | _appEnvironment = Get.find(); 20 | 21 | return super.resolveDependencies(); 22 | } 23 | 24 | @override 25 | Future load() async { 26 | await super.load(); 27 | 28 | scrollController = ScrollController(); 29 | 30 | final name = _appEnvironment.name; 31 | final version = _appEnvironment.version; 32 | final build = _appEnvironment.build; 33 | 34 | leafyVersion = '$name $version ($build)'; 35 | } 36 | 37 | Future openGithub() async { 38 | if (await canLaunch(_appEnvironment.github)) { 39 | await launch(_appEnvironment.github); 40 | } 41 | } 42 | 43 | Future openGmail() async { 44 | final link = 'mailto:${_appEnvironment.email}'; 45 | 46 | if (await canLaunch(link)) { 47 | await launch(link); 48 | } 49 | } 50 | 51 | Future openTelegram() async { 52 | if (await canLaunch(_appEnvironment.telegramChat)) { 53 | await launch(_appEnvironment.telegramChat); 54 | } 55 | } 56 | 57 | void onTitleTapped() { 58 | if (scrollController.hasClients) { 59 | scrollController.animateTo( 60 | .0, 61 | duration: kDefaultAnimationDuration, 62 | curve: kDefaultAnimationCurve, 63 | ); 64 | } 65 | } 66 | 67 | Future? openOss() { 68 | return Get.toNamed(AppRoutes.settingsOss); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss_license/home_settings_oss_license_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:leafy_launcher/resources/app_constants.dart'; 4 | import 'package:leafy_launcher/services/oss_licenses/oss_license.dart'; 5 | import 'package:leafy_launcher/services/oss_licenses/oss_licenses_service.dart'; 6 | 7 | import 'package:url_launcher/url_launcher.dart'; 8 | 9 | import '../../../base/controller/status_controller_base.dart'; 10 | 11 | class HomeSettingsOssLicenseController extends StatusControllerBase { 12 | HomeSettingsOssLicenseController(this._name); 13 | 14 | final String _name; 15 | 16 | late final OssLicensesService _ossLicensesService; 17 | late final ScrollController scrollController; 18 | 19 | late final OssLicense _ossLicense; 20 | late final String title; 21 | late final String license; 22 | late final String? description; 23 | late final String? homepage; 24 | 25 | @override 26 | Future resolveDependencies() { 27 | _ossLicensesService = Get.find(); 28 | return super.resolveDependencies(); 29 | } 30 | 31 | @override 32 | Future load() async { 33 | await super.load(); 34 | 35 | scrollController = ScrollController(); 36 | 37 | final ossLicense = _ossLicensesService.findByName(_name); 38 | 39 | if (ossLicense == null) { 40 | statusError(); 41 | 42 | return; 43 | } 44 | 45 | _ossLicense = ossLicense; 46 | 47 | title = '${_ossLicense.name} ${_ossLicense.version}'; 48 | description = _ossLicense.description; 49 | homepage = _ossLicense.homepage; 50 | license = _ossLicense.license; 51 | } 52 | 53 | Future openHomepage() async { 54 | final homepage = this.homepage; 55 | 56 | if (homepage == null) { 57 | return; 58 | } 59 | 60 | if (await canLaunch(homepage)) { 61 | await launch(homepage); 62 | } 63 | } 64 | 65 | void onTitleTapped() { 66 | if (scrollController.hasClients) { 67 | scrollController.animateTo( 68 | .0, 69 | duration: kDefaultAnimationDuration, 70 | curve: kDefaultAnimationCurve, 71 | ); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/items/leafy_section_custom_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/shared_widget/leafy_spacer.dart'; 3 | import 'package:leafy_launcher/shared_widget/section/src/theme/leafy_section_theme.dart'; 4 | 5 | class LeafySectionCustomItem extends StatelessWidget { 6 | const LeafySectionCustomItem({ 7 | Key? key, 8 | required this.title, 9 | this.subtitle, 10 | this.leading, 11 | this.value, 12 | this.onTap, 13 | this.onLongPress, 14 | }) : super(key: key); 15 | 16 | final Widget title; 17 | final Widget? subtitle; 18 | final Widget? leading; 19 | final Widget? value; 20 | final VoidCallback? onTap; 21 | final VoidCallback? onLongPress; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final theme = context.leafySectionTheme!; 26 | 27 | return InkWell( 28 | onTap: onTap, 29 | onLongPress: onLongPress, 30 | splashColor: Colors.transparent, 31 | child: Container( 32 | margin: EdgeInsets.symmetric( 33 | horizontal: theme.itemHorizontalPadding, 34 | vertical: theme.itemVerticalPadding, 35 | ), 36 | child: Row( 37 | children: [ 38 | if (leading != null) 39 | Container( 40 | margin: EdgeInsets.only( 41 | right: theme.itemHorizontalPadding, 42 | ), 43 | width: theme.leadingWidth, 44 | child: leading, 45 | ) 46 | else 47 | SizedBox( 48 | width: theme.leadingAlwaysTakesSpace ? theme.leadingWidth : .0, 49 | ), 50 | Expanded( 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | mainAxisSize: MainAxisSize.min, 54 | children: [ 55 | title, 56 | if (subtitle != null) const LeafySpacer(multipler: .5), 57 | if (subtitle != null) subtitle!, 58 | ], 59 | ), 60 | ), 61 | if (value != null) value!, 62 | ], 63 | ), 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/shared_widget/section/src/section/leafy_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/leafy_spacer.dart'; 4 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 5 | 6 | import '../theme/leafy_section_theme.dart'; 7 | import 'widget/leafy_section_footer.dart'; 8 | import 'widget/leafy_section_header.dart'; 9 | 10 | class LeafySection extends ThemedWidget { 11 | const LeafySection({ 12 | Key? key, 13 | this.header, 14 | this.footer, 15 | required this.children, 16 | }) : super(key: key); 17 | 18 | final String? header; 19 | final String? footer; 20 | final Iterable children; 21 | 22 | @override 23 | Widget body(BuildContext context, LeafyTheme theme) { 24 | final theme = context.leafySectionTheme!; 25 | 26 | return Column( 27 | crossAxisAlignment: CrossAxisAlignment.start, 28 | children: [ 29 | if (header != null) 30 | Padding( 31 | padding: EdgeInsets.only( 32 | left: theme.itemHorizontalPadding, 33 | right: theme.itemHorizontalPadding, 34 | ), 35 | child: LeafySectionHeader(title: header!), 36 | ), 37 | if (footer != null) const LeafySpacer(), 38 | if (footer != null) 39 | Padding( 40 | padding: EdgeInsets.only( 41 | left: theme.itemHorizontalPadding, 42 | right: theme.itemHorizontalPadding, 43 | ), 44 | child: LeafySectionFooter(title: footer!), 45 | ), 46 | if (header != null) const LeafySpacer(multipler: .5), 47 | Material( 48 | color: theme.backgroundColor, 49 | borderRadius: theme.sectionBorderRadius, 50 | clipBehavior: Clip.antiAlias, 51 | child: ListView.builder( 52 | shrinkWrap: true, 53 | physics: const NeverScrollableScrollPhysics(), 54 | itemCount: children.length, 55 | itemBuilder: (_, index) { 56 | return children.elementAt(index); 57 | }, 58 | ), 59 | ), 60 | ], 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/shared_widget/context_menu/context_menu_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:leafy_launcher/resources/theme/leafy_theme.dart'; 3 | import 'package:leafy_launcher/shared_widget/context_menu/context_menu_route.dart'; 4 | import 'package:leafy_launcher/shared_widget/leafy_text_button.dart'; 5 | import 'package:leafy_launcher/shared_widget/themed_widget.dart'; 6 | 7 | class ContextMenuButton 8 | extends ThemedWidget { 9 | const ContextMenuButton({ 10 | Key? key, 11 | required this.child, 12 | required this.items, 13 | this.offset = Offset.zero, 14 | }) : super(key: key); 15 | 16 | final Widget child; 17 | final List items; 18 | final Offset offset; 19 | 20 | Future _showMenu(BuildContext context) async { 21 | final focusScope = FocusScope.of(context); 22 | if (focusScope.hasFocus) { 23 | focusScope.unfocus(); 24 | } 25 | 26 | final renderObject = context.findRenderObject(); 27 | 28 | if (renderObject is! RenderBox) { 29 | throw Exception("Context's RenderBox was not found"); 30 | } 31 | 32 | final overlayRenderBox = Overlay.of(context)!.context.findRenderObject(); 33 | 34 | if (overlayRenderBox is! RenderBox) { 35 | throw Exception("Context Overlay's RenderBox was not found"); 36 | } 37 | 38 | final position = RelativeRect.fromRect( 39 | Rect.fromPoints( 40 | renderObject.localToGlobal( 41 | renderObject.size.bottomRight(Offset.zero), 42 | ancestor: overlayRenderBox, 43 | ), 44 | renderObject.localToGlobal( 45 | renderObject.size.bottomRight(Offset.zero), 46 | ancestor: overlayRenderBox, 47 | ), 48 | ), 49 | offset & overlayRenderBox.size, 50 | ); 51 | 52 | // TODO: Add an Alignment property and calculate this 53 | // based on that property. 54 | await ContextMenuRoute.open( 55 | context, 56 | Offset(position.left, position.top - items.length * 48), 57 | .0, 58 | items, 59 | ); 60 | } 61 | 62 | @override 63 | Widget body(BuildContext context, LeafyTheme theme) { 64 | return LeafyTextButton.circled( 65 | onPressed: () => _showMenu(context), 66 | child: child, 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/module/home_settings/oss_license/body/settings_oss_license_body.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/svg.dart'; 3 | import 'package:leafy_launcher/resources/assets/leafy_icons.dart'; 4 | import 'package:leafy_launcher/shared_widget/section/leafy_section_lib.dart'; 5 | import 'package:leafy_launcher/shared_widget/section/src/list/leafy_section_list.dart'; 6 | 7 | import '../../../../resources/localization/l10n.dart'; 8 | import '../../../../resources/localization/l10n_provider.dart'; 9 | import '../../../../resources/theme/home_theme.dart'; 10 | import '../../../../resources/theme/leafy_theme.dart'; 11 | import '../../../../shared_widget/themed_get_widget.dart'; 12 | import '../home_settings_oss_license_controller.dart'; 13 | 14 | class SettingsOssLicenseBody 15 | extends ThemedGetWidget { 16 | const SettingsOssLicenseBody({Key? key}) : super(key: key); 17 | 18 | @override 19 | Widget body(BuildContext context, LeafyTheme theme) { 20 | return LeafySectionList( 21 | scrollController: controller.scrollController, 22 | sections: [ 23 | LeafySection( 24 | header: L10nProvider.getText( 25 | L10n.settingsAboutOssLicenseDescriptionHeader, 26 | ), 27 | footer: controller.description, 28 | children: const [], 29 | ), 30 | if (controller.homepage != null) 31 | LeafySection( 32 | header: L10nProvider.getText( 33 | L10n.settingsAboutOssLicenseHomepageHeader, 34 | ), 35 | children: [ 36 | LeafySectionTextItem( 37 | title: L10nProvider.getText(L10n.settingsAboutOssOpenWebsite), 38 | leading: SvgPicture.asset( 39 | LeafyIcons.web, 40 | color: theme.foregroundColor, 41 | ), 42 | onTap: controller.openHomepage, 43 | ) 44 | ], 45 | ), 46 | LeafySection( 47 | header: L10nProvider.getText( 48 | L10n.settingsAboutOssLicenseLicenseHeader, 49 | ), 50 | footer: controller.license, 51 | children: const [], 52 | ), 53 | ], 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/shared_widget/list/list_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../resources/app_constants.dart'; 4 | import '../leafy_spacer.dart'; 5 | 6 | enum SeparatorType { line, space, none } 7 | 8 | class ListBuilder extends StatelessWidget { 9 | const ListBuilder({ 10 | required this.builder, 11 | required this.items, 12 | this.separatorType = SeparatorType.line, 13 | this.padding, 14 | this.addBottomBarPadding = true, 15 | this.spacing, 16 | this.scrollController, 17 | this.physics = 18 | const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()), 19 | }); 20 | 21 | final Widget Function(T item) builder; 22 | final Iterable items; 23 | final SeparatorType separatorType; 24 | final bool addBottomBarPadding; 25 | final EdgeInsets? padding; 26 | final double? spacing; 27 | final ScrollController? scrollController; 28 | final ScrollPhysics physics; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | if (separatorType != SeparatorType.none) { 33 | return ListView.separated( 34 | controller: scrollController, 35 | padding: padding, 36 | physics: physics, 37 | itemCount: items.length, 38 | itemBuilder: (_, index) { 39 | return builder(items.elementAt(index)); 40 | }, 41 | separatorBuilder: (_, index) { 42 | if (index == items.length - 1) { 43 | return SizedBox(height: spacing ?? kDefaultPadding * 3.0); 44 | } 45 | 46 | if (separatorType == SeparatorType.line) { 47 | return spacing == null 48 | ? const Divider(height: kDefaultPadding * 3.0) 49 | : Divider( 50 | height: spacing, 51 | ); 52 | } else { 53 | return spacing == null 54 | ? const LeafySpacer(multipler: 3.0) 55 | : SizedBox(height: spacing); 56 | } 57 | }, 58 | ); 59 | } 60 | 61 | return ListView.builder( 62 | controller: scrollController, 63 | padding: padding, 64 | physics: const BouncingScrollPhysics(), 65 | itemCount: items.length, 66 | itemBuilder: (_, index) { 67 | return builder(items.elementAt(index)); 68 | }, 69 | ); 70 | } 71 | } 72 | --------------------------------------------------------------------------------