├── .fvm └── fvm_config.json ├── .github └── workflows │ ├── build-android.yml │ └── publish-play-store.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── dev │ │ └── res │ │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── vauvenal5 │ │ │ │ └── yaga │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-anydpi-v24 │ │ │ └── ic_bg_service_small.xml │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ └── ic_launcher.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── file_paths_yaga.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── assets ├── icon │ ├── background.dev.svg │ ├── background.svg │ ├── foreground.svg │ ├── foreground_no_border.svg │ ├── icon.png │ ├── icon.svg │ └── readme.md └── news.md ├── build.yaml ├── fastlane ├── Appfile ├── Fastfile └── metadata │ └── android │ └── en-US │ ├── changelogs │ ├── 134.txt │ ├── 140.txt │ ├── 141.txt │ ├── 142.txt │ ├── 143.txt │ ├── 144.txt │ ├── 145.txt │ ├── 146.txt │ ├── 147.txt │ ├── 150.txt │ ├── 151.txt │ ├── 1510.txt │ ├── 1511.txt │ ├── 152.txt │ ├── 153.txt │ ├── 154.txt │ ├── 155.txt │ ├── 156.txt │ ├── 157.txt │ ├── 158.txt │ ├── 159.txt │ ├── 1600.txt │ ├── 1601.txt │ ├── 1602.txt │ ├── 1603.txt │ ├── 1700.txt │ ├── 1701.txt │ ├── 1702.txt │ ├── 1703.txt │ ├── 1704.txt │ ├── 1705.txt │ ├── 1800.txt │ ├── 1801.txt │ ├── 1802.txt │ ├── 1900.txt │ ├── 1901.txt │ ├── 2000.txt │ ├── 2001.txt │ ├── 2002.txt │ ├── 2003.txt │ ├── 2100.txt │ ├── 2101.txt │ ├── 2102.txt │ ├── 2103.txt │ ├── 2104.txt │ ├── 2105.txt │ ├── 2200.txt │ ├── 2201.txt │ ├── 2202.txt │ ├── 2203.txt │ ├── 2204.txt │ ├── 2205.txt │ ├── 2206.txt │ ├── 2207.txt │ ├── 2208.txt │ ├── 2300.txt │ ├── 2301.txt │ ├── 2302.txt │ ├── 2303.txt │ ├── 2304.txt │ ├── 2305.txt │ ├── 2306.txt │ ├── 2307.txt │ ├── 2308.txt │ ├── 2309.txt │ ├── 2310.txt │ ├── 2311.txt │ ├── 2312.txt │ ├── 2400.txt │ ├── 2500.txt │ ├── 2501.txt │ ├── 2502.txt │ ├── 2503.txt │ ├── 2600.txt │ ├── 2601.txt │ ├── 2700.txt │ ├── 2701.txt │ ├── 2800.txt │ ├── 2801.txt │ ├── 2802.txt │ ├── 2803.txt │ ├── 2804.txt │ ├── 2805.txt │ ├── 2900.txt │ ├── 2901.txt │ ├── 2902.txt │ ├── 3000.txt │ ├── 3001.txt │ ├── 3002.txt │ ├── 3003.txt │ ├── 4000.txt │ ├── 4001.txt │ ├── 4002.txt │ ├── 4003.txt │ ├── 4004.txt │ ├── 4005.txt │ ├── 4100.txt │ ├── 4200.txt │ ├── 4201.txt │ ├── 4300.txt │ └── 4301.txt │ ├── full_description.txt │ ├── images │ ├── featureGraphic.png │ ├── featureGraphic.svg │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── short_description.txt │ └── title.txt ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── main.dart ├── managers │ ├── file_manager │ │ ├── file_manager.dart │ │ ├── file_manager_base.dart │ │ └── isolateable │ │ │ ├── file_action_manager.dart │ │ │ └── isolated_file_manager.dart │ ├── file_service_manager │ │ ├── favorite_not_supported_mixin.dart │ │ ├── file_service_manager.dart │ │ ├── isolateable │ │ │ ├── local_file_manager.dart │ │ │ ├── nextcloud_background_file_manager.dart │ │ │ └── nextcloud_file_manger.dart │ │ └── media_file_manager.dart │ ├── global_settings_manager.dart │ ├── isolateable │ │ ├── isolated_global_settings_manager.dart │ │ ├── isolated_settings_manager.dart │ │ ├── mapping_manager.dart │ │ ├── sort_manager.dart │ │ └── sync_manager.dart │ ├── navigation_manager.dart │ ├── nextcloud_manager.dart │ ├── settings_manager.dart │ ├── settings_manager_base.dart │ ├── tab_manager.dart │ └── widget_local │ │ └── file_list_local_manager.dart ├── model │ ├── category_view_config.dart │ ├── fetched_file.dart │ ├── general_view_config.dart │ ├── local_file.dart │ ├── mapping_node.dart │ ├── nc_file.dart │ ├── nc_login_data.dart │ ├── nc_origin.dart │ ├── preferences │ │ ├── action_preference.dart │ │ ├── bool_preference.dart │ │ ├── choice_preference.dart │ │ ├── complex_preference.dart │ │ ├── int_preference.dart │ │ ├── mapping_preference.dart │ │ ├── preference.dart │ │ ├── section_preference.dart │ │ ├── serializable_preference.dart │ │ ├── serializers │ │ │ ├── base_type_serializer.dart │ │ │ ├── int_serializer.dart │ │ │ └── uri_serializer.dart │ │ ├── string_list_preference.dart │ │ ├── string_preference.dart │ │ ├── uri_preference.dart │ │ └── value_preference.dart │ ├── preview_fetch_meta.dart │ ├── route_args │ │ ├── choice_selector_screen_arguments.dart │ │ ├── directory_navigation_screen_arguments.dart │ │ ├── focus_view_arguments.dart │ │ ├── image_screen_arguments.dart │ │ ├── navigatable_screen_arguments.dart │ │ ├── path_selector_screen_arguments.dart │ │ └── settings_screen_arguments.dart │ ├── sort_config.dart │ ├── sorted_category_list.dart │ ├── sorted_file_folder_list.dart │ ├── sorted_file_list.dart │ ├── sync_file.dart │ └── system_location.dart ├── services │ ├── file_provider_service.dart │ ├── intent_service.dart │ ├── isolateable │ │ ├── local_file_service.dart │ │ ├── nextcloud_service.dart │ │ └── system_location_service.dart │ ├── media_file_service.dart │ ├── name_exchange_service.dart │ ├── secure_storage_service.dart │ ├── service.dart │ ├── shared_preferences_service.dart │ └── uri_name_resolver.dart ├── utils │ ├── background_worker │ │ ├── background_channel.dart │ │ ├── background_commands.dart │ │ ├── background_worker.dart │ │ ├── json_convertable.dart │ │ ├── messages │ │ │ ├── background_downloaded_request.dart │ │ │ └── background_init_msg.dart │ │ └── work_tracker.dart │ ├── download_file_image.dart │ ├── forground_worker │ │ ├── bridges │ │ │ ├── file_manager_bridge.dart │ │ │ ├── nextcloud_manager_bridge.dart │ │ │ └── settings_manager_bridge.dart │ │ ├── foreground_worker.dart │ │ ├── handlers │ │ │ ├── file_list_request_handler.dart │ │ │ ├── nextcloud_file_manager_handler.dart │ │ │ └── user_handler.dart │ │ ├── isolate_handler_regestry.dart │ │ ├── isolate_msg_handler.dart │ │ ├── isolateable.dart │ │ └── messages │ │ │ ├── download_file_request.dart │ │ │ ├── download_preview_complete.dart │ │ │ ├── download_preview_request.dart │ │ │ ├── file_list_done.dart │ │ │ ├── file_list_message.dart │ │ │ ├── file_list_request.dart │ │ │ ├── file_list_response.dart │ │ │ ├── file_update_msg.dart │ │ │ ├── files_action │ │ │ ├── delete_files_request.dart │ │ │ ├── destination_action_files_request.dart │ │ │ ├── favorite_files_request.dart │ │ │ ├── files_action_done.dart │ │ │ └── files_action_request.dart │ │ │ ├── flush_logs_message.dart │ │ │ ├── image_update_msg.dart │ │ │ ├── init_msg.dart │ │ │ ├── login_state_msg.dart │ │ │ ├── merge_sort_done.dart │ │ │ ├── merge_sort_request.dart │ │ │ ├── message.dart │ │ │ ├── preference_msg.dart │ │ │ ├── single_file_message.dart │ │ │ └── sort_request.dart │ ├── log_error_file_handler.dart │ ├── logger.dart │ ├── navigation │ │ ├── yaga_route_information_parser.dart │ │ ├── yaga_router.dart │ │ └── yaga_router_delegate.dart │ ├── ncfile_stream_extensions.dart │ ├── nextcloud_client_factory.dart │ ├── nextcloud_colors.dart │ ├── self_signed_cert_handler.dart │ ├── service_locator.dart │ └── uri_utils.dart └── views │ ├── screens │ ├── browse_view.dart │ ├── category_view_screen.dart │ ├── choice_selector_screen.dart │ ├── directory_screen.dart │ ├── directory_traversal_screen.dart │ ├── favorites_view.dart │ ├── focus_view.dart │ ├── home_view.dart │ ├── image_screen.dart │ ├── nc_address_screen.dart │ ├── nc_login_screen.dart │ ├── path_selector_screen.dart │ ├── settings_screen.dart │ ├── splash_screen.dart │ └── yaga_home_screen.dart │ └── widgets │ ├── action_danger_dialog.dart │ ├── address_form_advanced.dart │ ├── address_form_simple.dart │ ├── avatar_widget.dart │ ├── circle_avatar_icon.dart │ ├── favorite_icon.dart │ ├── folder_icon.dart │ ├── image_search.dart │ ├── image_view_container.dart │ ├── image_views │ ├── category_view.dart │ ├── category_view_exp.dart │ ├── nc_grid_view.dart │ ├── nc_list_view.dart │ └── utils │ │ ├── grid_delegate.dart │ │ └── view_configuration.dart │ ├── list_menu_entry.dart │ ├── path_widget.dart │ ├── preferences │ ├── action_preference_widget.dart │ ├── bool_preference_widget.dart │ ├── choice_preference_widget.dart │ ├── int_preference_widget.dart │ ├── mapping_preference_widget.dart │ ├── preference_list_tile_widget.dart │ ├── section_preference_widget.dart │ ├── string_preference_widget.dart │ └── uri_preference_widget.dart │ ├── remote_folder_widget.dart │ ├── remote_image_widget.dart │ ├── search_icon_button.dart │ ├── select_cancel_bottom_navigation.dart │ ├── selection_action_cancel_dialog.dart │ ├── selection_app_bar.dart │ ├── selection_popup_menu_button.dart │ ├── selection_select_all.dart │ ├── selection_title.dart │ ├── selection_will_pop_scope.dart │ ├── yaga_about_dialog.dart │ ├── yaga_bottom_nav_bar.dart │ ├── yaga_drawer.dart │ └── yaga_popup_menu_button.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── pubspec.lock ├── pubspec.yaml └── test ├── managers └── isolateable │ └── mapping_manager_test.dart ├── utils └── uri_utils_test.dart └── widget_test.dart /.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "flutterSdkVersion": "3.16.3", 3 | "flavors": {} 4 | } -------------------------------------------------------------------------------- /.github/workflows/publish-play-store.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Play Store 2 | on: 3 | workflow_call: 4 | inputs: 5 | stage: 6 | required: true 7 | type: string 8 | secrets: 9 | googlePlayJsonBase64: 10 | required: true 11 | jobs: 12 | publish: 13 | environment: ${{ inputs.stage }} 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/download-artifact@v3 18 | if: ${{ inputs.stage == 'internal' || inputs.stage == 'github'}} 19 | with: 20 | name: app-play-release.aab 21 | path: ./build/app/outputs/bundle/playRelease 22 | - uses: actions/download-artifact@v3 23 | if: ${{ inputs.stage == 'github' }} 24 | with: 25 | name: app-play-release.apk 26 | path: ./build/app/outputs/flutter-apk 27 | - uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: '3.0' # Not needed with a .ruby-version file 30 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 31 | - name: Create Android Keystore 32 | id: android_keystore 33 | uses: timheuer/base64-to-file@v1.2 34 | with: 35 | fileName: 'google-play.json' 36 | fileDir: './android/' 37 | encodedString: ${{ secrets.googlePlayJsonBase64 }} 38 | - run: cd fastlane 39 | - run: bundle exec fastlane ${{ inputs.stage }} 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | docker 12 | docker/.dev 13 | 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 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 | # Exceptions to above rules. 39 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 40 | android/key.properties 41 | fastlane/README.md 42 | fastlane/report.xml 43 | android/google-play.json 44 | **/*.g.dart 45 | .env 46 | 47 | # Generated by flutter_launcher_icons package 48 | android/app/src/main/res/mipmap-hdpi/ic_launcher.png 49 | android/app/src/main/res/mipmap-mdpi/ic_launcher.png 50 | android/app/src/main/res/mipmap-xhdpi/ic_launcher.png 51 | android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png 52 | android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png 53 | 54 | .fvm/flutter_sdk 55 | /yaga.jks 56 | /yaga.jks.base64 57 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "b0366e0a3f089e15fd89c97604ab402fe26b724c" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c 17 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c 18 | - platform: linux 19 | create_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c 20 | base_revision: b0366e0a3f089e15fd89c97604ab402fe26b724c 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /.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": "Flutter", 9 | "request": "launch", 10 | "args": [ 11 | "--flavor", 12 | "play" 13 | ], 14 | "type": "dart" 15 | }, 16 | { 17 | "name": "Flutter Profile", 18 | "request": "launch", 19 | "args": [ 20 | "--flavor", 21 | "play" 22 | ], 23 | "type": "dart", 24 | "flutterMode": "profile" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:lint/analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | sort_pub_dependencies: false 6 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | /google-play.json.base64 9 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/dev/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-anydpi-v24/ic_bg_service_small.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 16 | 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #0082c9 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths_yaga.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /assets/icon/background.dev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icon/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/assets/icon/icon.png -------------------------------------------------------------------------------- /assets/icon/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 57 | 70 | -------------------------------------------------------------------------------- /assets/icon/readme.md: -------------------------------------------------------------------------------- 1 | # Android Icons 2 | 3 | The SVG files found in this directory are the lead files for the app icons. 4 | 5 | For API 26+ changes to the SVGs have to be manually propagated to the respective VectorDrawables in `../android/app/src/main/resources/drawable`. 6 | 7 | For APIs smaller then 26 a `icon.png` has to be created from `icon.svg`. Then run the following command to generate the required icons. 8 | 9 | ```sh 10 | flutter pub run flutter_launcher_icons:main -f pubspec.yaml 11 | ``` -------------------------------------------------------------------------------- /assets/news.md: -------------------------------------------------------------------------------- 1 | ## What's New 2 | **Breaking Changes:** Started with refactoring for Android 11+ support. 3 | 4 | This has major impact on the app, please consider raising any issues you discover on Github. 5 | 6 | Refer to the docs for more information. 7 | 8 | **FDroid release will not be updated for the time being if you need any of the removed features and are still on Android 10 (API Level 29) or lower consider installing from there.** 9 | 10 | ## Changelog 11 | - targeting Android 12 (API Level 31) 12 | - refactoring to MediaStore API for local file access 13 | - root mapping deprecated: will be refactored in future when full MediaStore API support is implemented 14 | - automatically resetting root mapping to revert back to default app directory 15 | - local move/copy are currently not supported 16 | - local delete is supported but requires user confirmation 17 | - on Android 12 (API 31+) you can assign MANAGE_MEDIA rights to the app to avoid delete confirmation dialog 18 | - SD card support is broken 19 | - file provider for other apps is fixed 20 | - about app dialog was updated to support news 21 | 22 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | auto_apply_builders: true 4 | builders: 5 | "built_value_generator:built_value": 6 | enabled: true 7 | generate_for: 8 | - lib/model/preferences/*.dart 9 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("./android/google-play.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | package_name("com.github.vauvenal5.yaga") # e.g. com.krausefx.app 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/134.txt: -------------------------------------------------------------------------------- 1 | - fixed general settings login/logout bug 2 | - added splashscreen -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/140.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/141.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/142.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode 3 | - minor improvement to drop down menus -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/143.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode 3 | - minor improvement to drop down menus 4 | - fix isolates to properly shutdown/startup on app state change -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/144.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode 3 | - minor improvement to drop down menus 4 | - fix isolates to properly shutdown/startup on app state change 5 | - fix isolate startup -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/145.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode 3 | - minor improvement to drop down menus 4 | - fix isolates to properly shutdown/startup on app state change 5 | - fix isolate startup 6 | - minor visual improvements 7 | - code improvements -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/146.txt: -------------------------------------------------------------------------------- 1 | - introduced focus mode to browse view 2 | - fix title in focus mode 3 | - minor improvement to drop down menus 4 | - fix isolates to properly shutdown/startup on app state change 5 | - fix isolate startup 6 | - minor visual improvements 7 | - code improvements 8 | - fix to laoding mapper setting -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/147.txt: -------------------------------------------------------------------------------- 1 | - fix recursive loading when offline 2 | - fix recursive loading for local device -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/150.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/151.txt: -------------------------------------------------------------------------------- 1 | - add ACTION_PICK filter -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1510.txt: -------------------------------------------------------------------------------- 1 | - fix memory leak when downloading a lot of previews 2 | - fix UI freeze when loading a lot of directories recursively -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1511.txt: -------------------------------------------------------------------------------- 1 | - fix mapping manager when mapping non root folder 2 | - fix url validation -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/152.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal 3 | - add ACTION_PICK filter 4 | - open default screen for image select -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/153.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal 3 | - add ACTION_PICK filter 4 | - open default screen for image select 5 | - fix removal of upstream removed folders -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/154.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal 3 | - add ACTION_PICK filter 4 | - open default screen for image select 5 | - fix removal of upstream removed folders 6 | - fix recursive update issue from browse to home view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/155.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal 3 | - add ACTION_PICK filter 4 | - open default screen for image select 5 | - fix removal of upstream removed folders 6 | - fix recursive update issue from browse to home view 7 | - fix memory leak -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/156.txt: -------------------------------------------------------------------------------- 1 | - implement GET_CONTENT handler 2 | - introduce navigator 2.0 to directory traversal 3 | - add ACTION_PICK filter 4 | - open default screen for image select 5 | - fix removal of upstream removed folders 6 | - fix recursive update issue from browse to home view 7 | - fix memory leak 8 | - fix browse view recursive issue when entering focus view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/157.txt: -------------------------------------------------------------------------------- 1 | This release is identical to v0.15.6. There were build changes for FDroid support. 2 | - implement GET_CONTENT handler 3 | - introduce navigator 2.0 to directory traversal 4 | - add ACTION_PICK filter 5 | - open default screen for image select 6 | - fix removal of upstream removed folders 7 | - fix recursive update issue from browse to home view 8 | - fix memory leak 9 | - fix browse view recursive issue when entering focus view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/158.txt: -------------------------------------------------------------------------------- 1 | - fix memory leak when downloading a lot of previews 2 | - fix UI freeze when loading a lot of directories recursively -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/159.txt: -------------------------------------------------------------------------------- 1 | - fix memory leak when downloading a lot of previews 2 | - fix UI freeze when loading a lot of directories recursively -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1600.txt: -------------------------------------------------------------------------------- 1 | - add switch to disable URL validation in login mask -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1601.txt: -------------------------------------------------------------------------------- 1 | - enable production logging for warn and err -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1602.txt: -------------------------------------------------------------------------------- 1 | - enable production logging for warn and err 2 | - correctly set log level -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1603.txt: -------------------------------------------------------------------------------- 1 | - fix username handling with special characters -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1700.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1701.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select 2 | - allow sharing of multiple images -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1702.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select 2 | - allow sharing of multiple images 3 | - fix ripple effect on image tiles -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1703.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select 2 | - allow sharing of multiple images 3 | - fix ripple effect on image tiles 4 | - fix NC behinde subpath issue 5 | - fix fixedOrigin propagation for root mapping -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1704.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select 2 | - allow sharing of multiple images 3 | - fix ripple effect on image tiles 4 | - fix NC behinde subpath issue 5 | - fix fixedOrigin propagation for root mapping 6 | - replace button bar with bottom nav to align style and fix reder issue -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1705.txt: -------------------------------------------------------------------------------- 1 | - introducing multi-select 2 | - allow sharing of multiple images 3 | - fix ripple effect on image tiles 4 | - fix NC behinde subpath issue 5 | - fix fixedOrigin propagation for root mapping 6 | - replace button bar with bottom nav to align style and fix reder issue 7 | - fix issue of app not being properly navigated to grant access site -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1800.txt: -------------------------------------------------------------------------------- 1 | - delete file locally and remotely 2 | - fix recursive remove on server detection -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1801.txt: -------------------------------------------------------------------------------- 1 | - delete file locally and remotely 2 | - fix recursive remove on server detection 3 | - fix broken communication after background -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1802.txt: -------------------------------------------------------------------------------- 1 | - fix bug when login name and user name are not the same -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1900.txt: -------------------------------------------------------------------------------- 1 | - add browser based login flow as alternative to webview -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/1901.txt: -------------------------------------------------------------------------------- 1 | - fix broken cancel button on login screen 2 | - fix endless loading indicator when preview download fails 3 | - fix show user agent in browser login flow 4 | - fix persisting unchanged mapping issue -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2000.txt: -------------------------------------------------------------------------------- 1 | - implement support for copying files 2 | - implement support for moving files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2001.txt: -------------------------------------------------------------------------------- 1 | - implement support for copying files 2 | - implement support for moving files 3 | - !!! please note: if file exists in destination it will be overwritten !!! -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2002.txt: -------------------------------------------------------------------------------- 1 | - implement support for copying files 2 | - implement support for moving files 3 | - ignore same destination actions 4 | - !!! please note: if file exists in destination it will be overwritten !!! -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2003.txt: -------------------------------------------------------------------------------- 1 | - implement support for copying files 2 | - implement support for moving files 3 | - ignore same destination actions 4 | - ask for overwrite mode -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2100.txt: -------------------------------------------------------------------------------- 1 | - implement avatar caching 2 | - disable multi-select in path selector view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2101.txt: -------------------------------------------------------------------------------- 1 | - implement avatar caching 2 | - disable multi-select in path selector view 3 | - add warning dialog for root mapping -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2102.txt: -------------------------------------------------------------------------------- 1 | - fix lost data issue by making delete syncs optional -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2103.txt: -------------------------------------------------------------------------------- 1 | - move sorting to background -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2104.txt: -------------------------------------------------------------------------------- 1 | - move sorting to background -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2105.txt: -------------------------------------------------------------------------------- 1 | - move sorting to background -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2200.txt: -------------------------------------------------------------------------------- 1 | - introducing simple support for self-signed certificates -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2201.txt: -------------------------------------------------------------------------------- 1 | - fix local name handling if file/folder containes encoded chars (#86) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2202.txt: -------------------------------------------------------------------------------- 1 | - fix local name handling if file/folder containes encoded chars (#86) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2203.txt: -------------------------------------------------------------------------------- 1 | - improve logging -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2204.txt: -------------------------------------------------------------------------------- 1 | - fix minor issue in special char handling 2 | - improve logging -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2205.txt: -------------------------------------------------------------------------------- 1 | - fix color gradient 2 | - refactor icon 3 | - fix jumping splash screen -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2206.txt: -------------------------------------------------------------------------------- 1 | - fix color gradient 2 | - refactor icon 3 | - fix jumping splash screen -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2207.txt: -------------------------------------------------------------------------------- 1 | - fix color gradient 2 | - refactor icon 3 | - fix jumping splash screen -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2208.txt: -------------------------------------------------------------------------------- 1 | - remove host check from unsigned cert checks 2 | - fix bug in auto-revoking on exception -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2300.txt: -------------------------------------------------------------------------------- 1 | - add ability to send logs from within app 2 | - improve logging 3 | - improve self-signed cert usability -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2301.txt: -------------------------------------------------------------------------------- 1 | - add ability to send logs from within app 2 | - improve logging 3 | - improve self-signed cert usability -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2302.txt: -------------------------------------------------------------------------------- 1 | - add ability to send logs from within app 2 | - improve logging 3 | - improve self-signed cert usability 4 | - fix for fdroid build -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2303.txt: -------------------------------------------------------------------------------- 1 | - improve logging in list error cases 2 | - replace loading indicator for previews with default image -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2304.txt: -------------------------------------------------------------------------------- 1 | - improve performance when loading previews for many files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2305.txt: -------------------------------------------------------------------------------- 1 | - improve performance when loading previews for many files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2306.txt: -------------------------------------------------------------------------------- 1 | - improve performance when loading previews for many files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2307.txt: -------------------------------------------------------------------------------- 1 | - improve performance when loading previews for many files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2308.txt: -------------------------------------------------------------------------------- 1 | - improve performance when loading previews for many files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2309.txt: -------------------------------------------------------------------------------- 1 | - fix broken preview of local files -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2310.txt: -------------------------------------------------------------------------------- 1 | - fix image scroll controler -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2311.txt: -------------------------------------------------------------------------------- 1 | - move download to background -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2312.txt: -------------------------------------------------------------------------------- 1 | - move download to background 2 | - fix bug in category search -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2400.txt: -------------------------------------------------------------------------------- 1 | - implement switch for auto persist on download -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2500.txt: -------------------------------------------------------------------------------- 1 | - implement download files select action -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2501.txt: -------------------------------------------------------------------------------- 1 | - fix url validation issue 2 | - improve logging -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2502.txt: -------------------------------------------------------------------------------- 1 | - major upgrade of dependencies 2 | - fixes folder names that look like ports issue -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2503.txt: -------------------------------------------------------------------------------- 1 | - add confirmation dialog for logout 2 | - re-work preview fetching -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2600.txt: -------------------------------------------------------------------------------- 1 | - experimental sd card support, pls read https://github.com/vauvenal5/yaga/issues/23 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2601.txt: -------------------------------------------------------------------------------- 1 | - experimental sd card support, pls read https://github.com/vauvenal5/yaga/issues/23 -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2700.txt: -------------------------------------------------------------------------------- 1 | - targeting Android 12 (API Level 31) 2 | - refactoring to MediaStore API for local file access 3 | - root mapping deprecated 4 | - automatically resetting root mapping 5 | - local move/copy are currently not supported 6 | - local delete is supported but requires user confirmation 7 | - on Android 12 (API 31+) you can assign MANAGE_MEDIA rights to avoid delete confirmation dialog 8 | - SD card support is broken 9 | - file provider for other apps is fixed 10 | - about app dialog was updated to support news -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2701.txt: -------------------------------------------------------------------------------- 1 | - targeting Android 12 (API Level 31) 2 | - refactoring to MediaStore API for local file access 3 | - root mapping deprecated 4 | - automatically resetting root mapping 5 | - local move/copy are currently not supported 6 | - local delete is supported but requires user confirmation 7 | - on Android 12 (API 31+) you can assign MANAGE_MEDIA rights to avoid delete confirmation dialog 8 | - SD card support is broken 9 | - file provider for other apps fixed 10 | - about app dialog was updated to support news 11 | - privacy policy link added -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2800.txt: -------------------------------------------------------------------------------- 1 | - moved file actions into background thread 2 | - background feature can be switched on and off in general settings -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2801.txt: -------------------------------------------------------------------------------- 1 | - moved file actions into background thread 2 | - background feature can be switched on and off in general settings -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2802.txt: -------------------------------------------------------------------------------- 1 | - add support for attach to functionality of Android (needed for wallpapers) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2803.txt: -------------------------------------------------------------------------------- 1 | - enable hidding of app bar in image view by tap -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2804.txt: -------------------------------------------------------------------------------- 1 | - enable hidding of app bar in image view by tap 2 | - enable hidding of android overlays -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2805.txt: -------------------------------------------------------------------------------- 1 | - enable hidding of app bar in image view by tap 2 | - enable hidding of android overlays -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2900.txt: -------------------------------------------------------------------------------- 1 | - implement slideshow feature -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2901.txt: -------------------------------------------------------------------------------- 1 | - implement slideshow feature 2 | - add wakelock -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/2902.txt: -------------------------------------------------------------------------------- 1 | - implement slideshow feature 2 | - add wakelock 3 | - add random mode 4 | - add back to beginning 5 | - add auto stop at end -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3000.txt: -------------------------------------------------------------------------------- 1 | - replace deprecated NC client with new one -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3001.txt: -------------------------------------------------------------------------------- 1 | - replace deprecated NC client with new one 2 | - fix file action dialog in foreground worker mode -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3002.txt: -------------------------------------------------------------------------------- 1 | - replace deprecated NC client with new one 2 | - fix file action dialog in foreground worker mode 3 | - refactor background bridge -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/3003.txt: -------------------------------------------------------------------------------- 1 | - minor fixes in file action handling 2 | - new Nextcloud client version -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4000.txt: -------------------------------------------------------------------------------- 1 | - implement favorites view -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4001.txt: -------------------------------------------------------------------------------- 1 | - mark favorites in lists and grids -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4002.txt: -------------------------------------------------------------------------------- 1 | - fix favorites icon color 2 | - migrate to newest nextcloud client version -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4003.txt: -------------------------------------------------------------------------------- 1 | - fix minor issue with special characters in folder names -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4004.txt: -------------------------------------------------------------------------------- 1 | - fix minor issue with special characters in folder names 2 | - internal testing of Github releases -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4005.txt: -------------------------------------------------------------------------------- 1 | - fix minor issue with special characters in folder names 2 | - internal testing of Github releases -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4100.txt: -------------------------------------------------------------------------------- 1 | - add minimal linux support 2 | - refactor views to support big screens 3 | - fix missing permission bug -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4200.txt: -------------------------------------------------------------------------------- 1 | - favorites can be set and removed from app 2 | - reworked select all to smart select 3 | - some code clean up -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4201.txt: -------------------------------------------------------------------------------- 1 | - fix bug when persisting image is disabled -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4300.txt: -------------------------------------------------------------------------------- 1 | - add download button to image view 2 | - add favorite button to image view 3 | - add keybindings for linux (left/right arrow --- navigate, page up/down --- navigate, f --- Favorite) -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/changelogs/4301.txt: -------------------------------------------------------------------------------- 1 | - fix offline support -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | Nextcloud Yaga is a Nextcloud first gallery app. It aimes to give you the full functionality of a modern gallery app while utilizing your private Nextcloud server as backend. 2 | 3 | You can find the documentation here: https://vauvenal5.github.io/yaga-docs/ 4 | 5 | For details on the development state and currently implemented features, have a look on the Github page. https://github.com/vauvenal5/yaga -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/featureGraphic.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vauvenal5/yaga/0ac666be88c93e5ae17533474f460260d67990f6/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A Nextcloud first gallery app. -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/title.txt: -------------------------------------------------------------------------------- 1 | Nextcloud Yaga -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | yaga 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /lib/managers/file_manager/file_manager_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:rx_command/rx_command.dart'; 3 | import 'package:yaga/managers/file_service_manager/file_service_manager.dart'; 4 | import 'package:yaga/model/fetched_file.dart'; 5 | import 'package:yaga/model/nc_file.dart'; 6 | import 'package:yaga/utils/forground_worker/messages/file_list_message.dart'; 7 | import 'package:yaga/utils/forground_worker/messages/file_update_msg.dart'; 8 | import 'package:yaga/utils/forground_worker/messages/files_action/files_action_done.dart'; 9 | 10 | /// File Managers provide files functions, i.e. copy/delete/move/download 11 | /// with context of required actions over multiple services 12 | abstract class FileManagerBase { 13 | RxCommand updateImageCommand = RxCommand.createSync((param) => param); 14 | late RxCommand fetchedFileCommand; 15 | 16 | //todo: Background: this should be moved out of here because it does not concern FileActionsManager 17 | RxCommand updateFilesCommand = RxCommand.createSync((param) => param); 18 | 19 | // responses 20 | RxCommand filesActionDoneCommand = RxCommand.createSync((param) => param); 21 | RxCommand fileUpdateMessage = RxCommand.createSync((param) => param); 22 | 23 | @protected 24 | Map fileServiceManagers = {}; 25 | 26 | FileManagerBase() { 27 | fetchedFileCommand = RxCommand.createSync((param) { 28 | updateImageCommand(param.file); 29 | return param; 30 | }); 31 | } 32 | 33 | void registerFileManager(FileServiceManager fileServiceManager) { 34 | fileServiceManagers.putIfAbsent(fileServiceManager.scheme, () => fileServiceManager); 35 | } 36 | 37 | //todo: fileManager refactoring: this method should not be callable from the UI 38 | Stream listFiles(Uri uri, {bool recursive = false}); 39 | } 40 | -------------------------------------------------------------------------------- /lib/managers/file_service_manager/favorite_not_supported_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | 3 | mixin FavoriteNotSupportedMixin { 4 | Future toggleFavorite(NcFile file,) => throw UnimplementedError(); 5 | } -------------------------------------------------------------------------------- /lib/managers/file_service_manager/file_service_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | 3 | /// FileServiceManagers are providing basic file manager functions 4 | /// encapsulating required actions for one specific source. 5 | abstract class FileServiceManager { 6 | final String scheme = ""; 7 | 8 | Stream listFiles(Uri uri, {bool recursive = false}); 9 | Stream> listFileList(Uri uri, {bool recursive = false, bool favorites = false,}); 10 | Future deleteFile(NcFile file, {required bool local}); 11 | Future copyFile(NcFile file, Uri destination, {bool overwrite = false}); 12 | Future moveFile(NcFile file, Uri destination, {bool overwrite = false}); 13 | Future toggleFavorite(NcFile file,); 14 | } 15 | -------------------------------------------------------------------------------- /lib/managers/file_service_manager/media_file_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:yaga/managers/file_service_manager/favorite_not_supported_mixin.dart'; 4 | import 'package:yaga/managers/file_service_manager/file_service_manager.dart'; 5 | import 'package:yaga/model/nc_file.dart'; 6 | 7 | import 'package:yaga/services/media_file_service.dart'; 8 | 9 | class MediaFileManager with FavoriteNotSupportedMixin implements FileServiceManager { 10 | final MediaFileService _mediaFileService; 11 | 12 | MediaFileManager(this._mediaFileService); 13 | 14 | @override 15 | Future deleteFile(NcFile file, {required bool local}) { 16 | return deleteFiles(List.filled(1, file)).then((value) => value.first); 17 | } 18 | 19 | Future> deleteFiles(List files) { 20 | return _mediaFileService.deleteFile(files); 21 | } 22 | 23 | @override 24 | Stream> listFileList(Uri uri, {bool recursive = false, bool favorites = false}) { 25 | // TODO: implement listFileList 26 | throw UnimplementedError(); 27 | } 28 | 29 | @override 30 | Stream listFiles(Uri uri, {bool recursive = false}) { 31 | return _mediaFileService.listFiles(uri); 32 | } 33 | 34 | @override 35 | // todo: currently this is reusing the file-scheme which is meant for local access probably should use a different one? 36 | String get scheme => _mediaFileService.scheme; 37 | 38 | @override 39 | Future copyFile(NcFile file, Uri destination, 40 | {bool overwrite = false}) { 41 | // TODO: implement copyFile 42 | throw UnimplementedError(); 43 | } 44 | 45 | @override 46 | Future moveFile(NcFile file, Uri destination, 47 | {bool overwrite = false}) { 48 | // TODO: implement moveFile 49 | throw UnimplementedError(); 50 | } 51 | 52 | bool isRelevant(String scheme) { 53 | return Platform.isAndroid && scheme == this.scheme; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/managers/isolateable/isolated_global_settings_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | 3 | import 'package:yaga/managers/global_settings_manager.dart'; 4 | import 'package:yaga/managers/isolateable/isolated_settings_manager.dart'; 5 | import 'package:yaga/model/preferences/bool_preference.dart'; 6 | import 'package:yaga/utils/forground_worker/isolateable.dart'; 7 | import 'package:yaga/utils/forground_worker/messages/init_msg.dart'; 8 | 9 | class IsolatedGlobalSettingsManager 10 | with Isolateable { 11 | @override 12 | Future initIsolated( 13 | InitMsg init, 14 | SendPort isolateToMain, 15 | ) async { 16 | _autoPersist = init.autoPersist; 17 | return this; 18 | } 19 | 20 | final IsolatedSettingsManager _settingsManager; 21 | 22 | late BoolPreference _autoPersist; 23 | BoolPreference get autoPersist => _autoPersist; 24 | 25 | IsolatedGlobalSettingsManager(this._settingsManager) { 26 | _settingsManager.updateSettingCommand 27 | .where((event) => event.key == GlobalSettingsManager.autoPersist.key) 28 | .map((event) => event as BoolPreference) 29 | .listen((value) => _autoPersist = value); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/managers/isolateable/isolated_settings_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | 3 | import 'package:yaga/managers/settings_manager_base.dart'; 4 | import 'package:yaga/utils/forground_worker/isolateable.dart'; 5 | import 'package:yaga/utils/forground_worker/messages/init_msg.dart'; 6 | 7 | class IsolatedSettingsManager extends SettingsManagerBase 8 | with Isolateable { 9 | @override 10 | Future initIsolated( 11 | InitMsg init, 12 | SendPort isolateToMain, 13 | ) async { 14 | if(init.mapping != null) { 15 | updateSettingCommand(init.mapping); 16 | } 17 | return this; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/managers/isolateable/sync_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/model/sync_file.dart'; 3 | import 'package:yaga/utils/forground_worker/isolateable.dart'; 4 | import 'package:yaga/utils/logger.dart'; 5 | 6 | class SyncManager with Isolateable { 7 | final _logger = YagaLogger.getLogger(SyncManager); 8 | final Map> _syncMatrix = {}; 9 | 10 | Future addUri(Uri key) async { 11 | _syncMatrix.putIfAbsent(key, () => {}); 12 | } 13 | 14 | SyncFile? _addFile(Uri key, NcFile file) { 15 | return _syncMatrix[key]?.putIfAbsent(file.uri, () => SyncFile(file)); 16 | } 17 | 18 | Future addFile(Uri key, NcFile file) async { 19 | _logger.fine("Adding file ${file.uri.path}"); 20 | _addFile(key, file); 21 | } 22 | 23 | Future addRemoteFile(Uri key, NcFile file) async { 24 | _logger.fine("Adding remote file ${file.uri.path}"); 25 | _addFile(key, file)?.remote = true; 26 | } 27 | 28 | Future> syncUri(Uri key) async { 29 | return _syncMatrix 30 | .remove(key) 31 | ?.values 32 | .where((file) => !file.remote) 33 | .map((e) => e.file) 34 | .toList() ?? 35 | const []; 36 | } 37 | 38 | Future removeUri(Uri key) async { 39 | _syncMatrix.remove(key); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/managers/navigation_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:yaga/model/route_args/directory_navigation_screen_arguments.dart'; 3 | 4 | class NavigationManager { 5 | RxCommand showDirectoryNavigation = 7 | RxCommand.createSync((param) => param, initialLastResult: null); 8 | } 9 | -------------------------------------------------------------------------------- /lib/managers/settings_manager_base.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:yaga/model/preferences/preference.dart'; 3 | 4 | abstract class SettingsManagerBase { 5 | late RxCommand updateSettingCommand; 6 | 7 | SettingsManagerBase() { 8 | updateSettingCommand = RxCommand.createSync((param) => param); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/managers/tab_manager.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 3 | 4 | class TabManager { 5 | RxCommand tabChangedCommand = 6 | RxCommand.createSync((param) => param); 7 | } 8 | -------------------------------------------------------------------------------- /lib/model/category_view_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/general_view_config.dart'; 2 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 3 | 4 | class CategoryViewConfig { 5 | // final Uri defaultPath; 6 | final String pref; 7 | final YagaHomeTab? selectedTab; 8 | final bool? hasDrawer; 9 | final bool favorites; 10 | 11 | // final bool pathEnabled; 12 | final String? title; 13 | final GeneralViewConfig generalViewConfig; 14 | 15 | CategoryViewConfig({ 16 | required Uri defaultPath, 17 | required this.pref, 18 | this.selectedTab, 19 | this.hasDrawer, 20 | required bool pathEnabled, 21 | this.title, 22 | this.favorites = false 23 | }) : generalViewConfig = 24 | GeneralViewConfig(pref, defaultPath, pathEnabled: pathEnabled); 25 | } 26 | -------------------------------------------------------------------------------- /lib/model/fetched_file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:yaga/model/nc_file.dart'; 4 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 5 | 6 | class FetchedFile extends Message { 7 | final NcFile file; 8 | final Uint8List data; 9 | 10 | FetchedFile(this.file, this.data) : super("FetchedFile"); 11 | } 12 | -------------------------------------------------------------------------------- /lib/model/general_view_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/preferences/preference.dart'; 3 | import 'package:yaga/model/preferences/section_preference.dart'; 4 | import 'package:yaga/model/preferences/uri_preference.dart'; 5 | 6 | class GeneralViewConfig { 7 | final SectionPreference general; 8 | final UriPreference path; 9 | 10 | factory GeneralViewConfig( 11 | String pref, 12 | Uri defaultPath, { 13 | required bool pathEnabled, 14 | }) { 15 | final SectionPreference general = SectionPreference((b) => b 16 | ..key = Preference.prefixKey(pref, "general") 17 | ..title = "General"); 18 | final UriPreference path = UriPreference((b) => b 19 | ..key = general.prepareKey("path") 20 | ..title = "Path" 21 | ..value = defaultPath 22 | ..enabled = pathEnabled); 23 | 24 | return GeneralViewConfig.internal(general, path); 25 | } 26 | 27 | @protected 28 | GeneralViewConfig.internal(this.general, this.path); 29 | } 30 | -------------------------------------------------------------------------------- /lib/model/local_file.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class LocalFile { 4 | static const _jsonFileUrl = "fileUrl"; 5 | static const _jsonExists = "exists"; 6 | 7 | FileSystemEntity file; 8 | bool exists; 9 | 10 | LocalFile(this.file, this.exists); 11 | 12 | LocalFile.fromJson(Map json, {required bool isDirectory}) 13 | : file = isDirectory 14 | ? Directory.fromUri(Uri.parse(json[_jsonFileUrl] as String)) 15 | : File.fromUri(Uri.parse(json[_jsonFileUrl] as String)), 16 | exists = json[_jsonExists] as bool; 17 | 18 | Map toJson() { 19 | return { 20 | _jsonFileUrl: file.uri.toString(), 21 | _jsonExists: exists, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/mapping_node.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/mapping_preference.dart'; 2 | 3 | class MappingNode { 4 | Map nodes = {}; 5 | MappingPreference? mapping; 6 | } 7 | -------------------------------------------------------------------------------- /lib/model/nc_login_data.dart: -------------------------------------------------------------------------------- 1 | class NextCloudLoginDataKeys { 2 | static const String server = "server"; 3 | static const String user = "user"; 4 | static const String password = "password"; 5 | static const String id = "id"; 6 | static const String displayName = "displayName"; 7 | } 8 | 9 | class NextCloudLoginData { 10 | final Uri? server; 11 | final String user; 12 | final String password; 13 | final String id; 14 | final String displayName; 15 | 16 | NextCloudLoginData( 17 | this.server, 18 | this.user, 19 | this.password, { 20 | this.id = "", 21 | this.displayName = "", 22 | }); 23 | 24 | factory NextCloudLoginData.empty() => NextCloudLoginData(null, "", "",); 25 | 26 | //todo: Background: use auto-generation for formJson/toJson? 27 | NextCloudLoginData.fromJson(Map json) 28 | : server = Uri.parse(json[NextCloudLoginDataKeys.server] as String), 29 | user = json[NextCloudLoginDataKeys.user] as String, 30 | password = json[NextCloudLoginDataKeys.password] as String, 31 | id = json[NextCloudLoginDataKeys.id] as String, 32 | displayName = json[NextCloudLoginDataKeys.displayName] as String; 33 | 34 | Map toJson() { 35 | return { 36 | NextCloudLoginDataKeys.server: server.toString(), 37 | NextCloudLoginDataKeys.user: user, 38 | NextCloudLoginDataKeys.password: password, 39 | NextCloudLoginDataKeys.id: id, 40 | NextCloudLoginDataKeys.displayName: displayName, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/model/nc_origin.dart: -------------------------------------------------------------------------------- 1 | class NcOrigin { 2 | /// This uri represents the original server uri with port and path if specified. 3 | /// Only the scheme is changed to 'nc'. 4 | /// 5 | /// In case of a local file: file://device.local 6 | /// 7 | /// Examples for valid [uri] values: 8 | /// * cloud.com: Domain 9 | /// * nc.cloud.com: Subdomain 10 | /// * www.cloud.com: Equal to subdomain. 11 | /// * www.cloud.com/nc: Nextcloud root behind subpath. 12 | /// * www.cloud.com:7443: Nextcloud root behind different port. 13 | final Uri uri; 14 | 15 | /// Examples for valid [username] values: 16 | /// * Simple username: xyz 17 | /// * Username with special characters: xyz@email.com 18 | /// * LDAP UUID: 2522ba7c2-xxxxxxxx-yyyyyy 19 | final String username; 20 | 21 | /// Display name, username, loign name can be different things in Nextcloud. 22 | /// Display name can be equal to the username or anything else like: Forename Lastname 23 | final String displayName; 24 | 25 | /// Can be an email or an username. 26 | final String loginName; 27 | 28 | NcOrigin( 29 | this.uri, 30 | this.username, 31 | this.displayName, 32 | this.loginName, 33 | ); 34 | 35 | String get domain => uri.host; 36 | //todo: when refactoring, how will we handle subpaths in origin for user home folder in app directory? 37 | /// We are using the login name for backwards compability however if a user logs out and in again with different login names 38 | /// then this user will not be mapped back to the right data folder. 39 | String get userDomain => "$loginName@${uri.host}"; 40 | 41 | /// This is the old representation of root. Still needed for the [MappingManager] to work properly. 42 | /// Returns uri in form: nc://user@host/ 43 | /// With user being encoded. 44 | /// We are using the login name for backwards compability however if a user logs out and in again with different login names 45 | /// then this user will not be mapped back to the right data folder. 46 | Uri get userEncodedDomainRoot => Uri( 47 | scheme: uri.scheme, 48 | userInfo: Uri.encodeComponent(loginName), 49 | host: uri.host, 50 | path: "/", 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /lib/model/preferences/action_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/preference.dart'; 5 | 6 | part 'action_preference.g.dart'; 7 | 8 | abstract class ActionPreference 9 | implements Preference, Built { 10 | Function() get action; 11 | 12 | static void _initializeBuilder(ActionPreferenceBuilder b) => 13 | Preference.initBuilder(b); 14 | 15 | factory ActionPreference([void Function(ActionPreferenceBuilder) updates]) = 16 | _$ActionPreference; 17 | ActionPreference._(); 18 | } 19 | -------------------------------------------------------------------------------- /lib/model/preferences/bool_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'bool_preference.g.dart'; 9 | 10 | abstract class BoolPreference 11 | with BaseTypeSerializer 12 | implements 13 | SerializablePreference, 14 | Built { 15 | static void _initializeBuilder(BoolPreferenceBuilder b) => 16 | ValuePreference.initBuilder(b); 17 | 18 | factory BoolPreference([void Function(BoolPreferenceBuilder) updates]) = 19 | _$BoolPreference; 20 | BoolPreference._(); 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/preferences/choice_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'choice_preference.g.dart'; 9 | 10 | abstract class ChoicePreference 11 | with BaseTypeSerializer 12 | implements 13 | SerializablePreference, 14 | Built { 15 | Map get choices; 16 | 17 | static void _initializeBuilder(ChoicePreferenceBuilder b) => 18 | ValuePreference.initBuilder(b); 19 | 20 | factory ChoicePreference([void Function(ChoicePreferenceBuilder) updates]) = 21 | _$ChoicePreference; 22 | ChoicePreference._(); 23 | } 24 | -------------------------------------------------------------------------------- /lib/model/preferences/complex_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:yaga/model/preferences/serializable_preference.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'complex_preference.g.dart'; 9 | 10 | //todo: is the bool value ever used? 11 | @BuiltValue(instantiable: false) 12 | abstract class ComplexPreference 13 | implements SerializablePreference { 14 | @protected 15 | static T initBuilder(T b) => 16 | ValuePreference.initBuilder(b); 17 | 18 | @override 19 | ComplexPreference rebuild(void Function(ComplexPreferenceBuilder) updates); 20 | @override 21 | ComplexPreferenceBuilder toBuilder(); 22 | } 23 | -------------------------------------------------------------------------------- /lib/model/preferences/int_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 6 | import 'package:yaga/model/preferences/serializers/int_serializer.dart'; 7 | import 'package:yaga/model/preferences/value_preference.dart'; 8 | 9 | part 'int_preference.g.dart'; 10 | 11 | abstract class IntPreference 12 | with BaseTypeSerializer 13 | implements 14 | SerializablePreference, 15 | Built { 16 | static void _initializeBuilder(IntPreferenceBuilder b) => 17 | ValuePreference.initBuilder(b); 18 | 19 | factory IntPreference([void Function(IntPreferenceBuilder) updates]) = 20 | _$IntPreference; 21 | IntPreference._(); 22 | } 23 | -------------------------------------------------------------------------------- /lib/model/preferences/mapping_preference.dart: -------------------------------------------------------------------------------- 1 | //todo: Preference clone functions should be unified 2 | library preference; 3 | 4 | import 'package:built_value/built_value.dart'; 5 | import 'package:yaga/model/preferences/bool_preference.dart'; 6 | import 'package:yaga/model/preferences/complex_preference.dart'; 7 | import 'package:yaga/model/preferences/preference.dart'; 8 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 9 | import 'package:yaga/model/preferences/uri_preference.dart'; 10 | 11 | part 'mapping_preference.g.dart'; 12 | 13 | abstract class MappingPreference 14 | with BaseTypeSerializer 15 | implements 16 | ComplexPreference, 17 | Built { 18 | UriPreference get remote; 19 | UriPreference get local; 20 | BoolPreference get syncDeletes; 21 | 22 | //todo: still need a solution for key prefixes 23 | static void _initializeBuilder(MappingPreferenceBuilder b) => 24 | ComplexPreference.initBuilder(b) 25 | ..value = true 26 | ..remote.key = "remote" 27 | ..remote.title = "Remote Path" 28 | //todo: scheme has to be retrieved from actual service 29 | ..remote.schemeFilter = "nc" 30 | ..local.key = "local" 31 | ..local.title = "Local Path" 32 | ..local.schemeFilter = "file" 33 | ..syncDeletes.key = "syncDeletes" 34 | ..syncDeletes.title = "Sync Server Deletes" 35 | ..syncDeletes.value = true; 36 | 37 | static void _finalizeBuilder(MappingPreferenceBuilder b) => b 38 | ..remote.key = Preference.prefixKey(b.key!, b.remote.key!) 39 | ..local.key = Preference.prefixKey(b.key!, b.local.key!) 40 | ..syncDeletes.key = Preference.prefixKey(b.key!, b.syncDeletes.key!); 41 | 42 | factory MappingPreference([void Function(MappingPreferenceBuilder) updates]) = 43 | _$MappingPreference; 44 | MappingPreference._(); 45 | } 46 | -------------------------------------------------------------------------------- /lib/model/preferences/preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | 6 | part 'preference.g.dart'; 7 | 8 | @BuiltValue(instantiable: false) 9 | abstract class Preference { 10 | String? get key; 11 | String? get title; 12 | bool? get enabled; 13 | 14 | static String prefixKey(String prefix, String key) => 15 | key.startsWith(prefix) ? key : "$prefix:$key"; 16 | 17 | @protected 18 | static PreferenceBuilder initBuilder( 19 | PreferenceBuilder b) => 20 | b..enabled = true; 21 | 22 | Preference rebuild(void Function(PreferenceBuilder) updates); 23 | PreferenceBuilder toBuilder(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/preferences/section_preference.dart: -------------------------------------------------------------------------------- 1 | //todo: refactor all preferences to respect enabled 2 | library preference; 3 | 4 | import 'package:built_value/built_value.dart'; 5 | import 'package:yaga/model/preferences/preference.dart'; 6 | 7 | part 'section_preference.g.dart'; 8 | 9 | abstract class SectionPreference 10 | implements Preference, Built { 11 | String prepareKey(String keyPart) => Preference.prefixKey(key!, keyPart); 12 | 13 | static void _initializeBuilder(SectionPreferenceBuilder b) => 14 | Preference.initBuilder(b); 15 | 16 | factory SectionPreference([void Function(SectionPreferenceBuilder) updates]) = 17 | _$SectionPreference; 18 | SectionPreference._(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/model/preferences/serializable_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:yaga/model/preferences/value_preference.dart'; 6 | 7 | part 'serializable_preference.g.dart'; 8 | 9 | @BuiltValue(instantiable: false) 10 | abstract class SerializablePreference> 12 | implements ValuePreference { 13 | @protected 14 | static T initBuilder( 15 | ValuePreferenceBuilder b) => 16 | ValuePreference.initBuilder(b); 17 | 18 | SerializableType serialize(); 19 | PreferenceType deserialize(SerializableType? value); 20 | 21 | @override 22 | PreferenceType rebuild( 23 | void Function( 24 | SerializablePreferenceBuilder) 26 | updates); 27 | @override 28 | SerializablePreferenceBuilder 29 | toBuilder(); 30 | } 31 | -------------------------------------------------------------------------------- /lib/model/preferences/serializers/base_type_serializer.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/serializable_preference.dart'; 2 | import 'package:yaga/model/preferences/value_preference.dart'; 3 | 4 | mixin BaseTypeSerializer> 5 | implements SerializablePreference { 6 | @override 7 | T serialize() { 8 | return value; 9 | } 10 | 11 | @override 12 | P deserialize(T? value) { 13 | return (value == null ? this : rebuild((b) => b..value = value)) as P; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/model/preferences/serializers/int_serializer.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/int_preference.dart'; 2 | import 'package:yaga/model/preferences/serializable_preference.dart'; 3 | 4 | mixin IntSerializer 5 | implements SerializablePreference { 6 | @override 7 | String serialize() { 8 | return value.toString(); 9 | } 10 | 11 | @override 12 | IntPreference deserialize(String? value) { 13 | return value == null 14 | ? this as IntPreference 15 | : rebuild((b) => b..value = int.parse(value)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/model/preferences/serializers/uri_serializer.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/serializable_preference.dart'; 2 | import 'package:yaga/model/preferences/uri_preference.dart'; 3 | 4 | mixin UriSerializer 5 | implements SerializablePreference { 6 | @override 7 | String serialize() { 8 | return value.toString(); 9 | } 10 | 11 | @override 12 | UriPreference deserialize(String? value) { 13 | return value == null 14 | ? this as UriPreference 15 | : rebuild((b) => b..value = Uri.parse(value)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/model/preferences/string_list_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'string_list_preference.g.dart'; 9 | 10 | abstract class StringListPreference 11 | with 12 | BaseTypeSerializer, StringListPreference> 13 | implements 14 | SerializablePreference, List, 15 | StringListPreference>, 16 | Built { 17 | static void _initializeBuilder(StringListPreferenceBuilder b) => 18 | ValuePreference.initBuilder(b); 19 | 20 | factory StringListPreference( 21 | [void Function(StringListPreferenceBuilder) updates]) = 22 | _$StringListPreference; 23 | StringListPreference._(); 24 | } 25 | -------------------------------------------------------------------------------- /lib/model/preferences/string_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/base_type_serializer.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'string_preference.g.dart'; 9 | 10 | abstract class StringPreference 11 | with BaseTypeSerializer 12 | implements 13 | SerializablePreference, 14 | Built { 15 | static void _initializeBuilder(StringPreferenceBuilder b) => 16 | ValuePreference.initBuilder(b); 17 | 18 | factory StringPreference([void Function(StringPreferenceBuilder) updates]) = 19 | _$StringPreference; 20 | StringPreference._(); 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/preferences/uri_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:yaga/model/preferences/serializable_preference.dart'; 5 | import 'package:yaga/model/preferences/serializers/uri_serializer.dart'; 6 | import 'package:yaga/model/preferences/value_preference.dart'; 7 | 8 | part 'uri_preference.g.dart'; 9 | 10 | abstract class UriPreference 11 | with UriSerializer 12 | implements 13 | SerializablePreference, 14 | Built { 15 | bool get fixedOrigin; 16 | String get schemeFilter; 17 | 18 | static void _initializeBuilder(UriPreferenceBuilder b) => 19 | ValuePreference.initBuilder(b) 20 | ..fixedOrigin = false 21 | ..schemeFilter = ""; 22 | 23 | factory UriPreference([void Function(UriPreferenceBuilder) updates]) = 24 | _$UriPreference; 25 | UriPreference._(); 26 | } 27 | -------------------------------------------------------------------------------- /lib/model/preferences/value_preference.dart: -------------------------------------------------------------------------------- 1 | library preference; 2 | 3 | import 'package:built_value/built_value.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:yaga/model/preferences/preference.dart'; 6 | 7 | part 'value_preference.g.dart'; 8 | 9 | @BuiltValue(instantiable: false) 10 | abstract class ValuePreference implements Preference { 11 | T get value; 12 | 13 | @protected 14 | static T initBuilder( 15 | ValuePreferenceBuilder b) => 16 | Preference.initBuilder(b) as T; 17 | 18 | @override 19 | ValuePreference rebuild(void Function(ValuePreferenceBuilder) updates); 20 | @override 21 | ValuePreferenceBuilder toBuilder(); 22 | } 23 | -------------------------------------------------------------------------------- /lib/model/preview_fetch_meta.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | 3 | class PreviewFetchMeta { 4 | final NcFile file; 5 | final int fetchIndex; 6 | final bool success; 7 | 8 | PreviewFetchMeta(this.file, this.fetchIndex, {this.success = true}); 9 | } 10 | -------------------------------------------------------------------------------- /lib/model/route_args/choice_selector_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/choice_preference.dart'; 2 | 3 | class ChoiceSelectorScreenArguments { 4 | final ChoicePreference choicePreference; 5 | final void Function() onCancel; 6 | final void Function(String) onSelect; 7 | 8 | ChoiceSelectorScreenArguments( 9 | this.choicePreference, this.onSelect, this.onCancel); 10 | } 11 | -------------------------------------------------------------------------------- /lib/model/route_args/directory_navigation_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/route_args/navigatable_screen_arguments.dart'; 3 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 4 | 5 | class DirectoryNavigationScreenArguments extends NavigatableScreenArguments { 6 | final ViewConfiguration viewConfig; 7 | final bool leadingBackArrow; 8 | final bool fixedOrigin; 9 | final String schemeFilter; 10 | final String title; 11 | final Widget Function(BuildContext, Uri)? bottomBarBuilder; 12 | 13 | DirectoryNavigationScreenArguments({ 14 | required Uri uri, 15 | required this.title, 16 | required this.viewConfig, 17 | this.bottomBarBuilder, 18 | this.leadingBackArrow = true, 19 | this.fixedOrigin = false, 20 | this.schemeFilter = "", 21 | }) : super(uri: uri); 22 | } 23 | -------------------------------------------------------------------------------- /lib/model/route_args/focus_view_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 2 | 3 | class FocusViewArguments { 4 | final Uri path; 5 | final bool favorites; 6 | final YagaHomeTab selected; 7 | final String prefPrefix; 8 | 9 | FocusViewArguments(this.path, this.favorites, this.selected, this.prefPrefix); 10 | } 11 | -------------------------------------------------------------------------------- /lib/model/route_args/image_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/nc_file.dart'; 3 | 4 | class ImageScreenArguments { 5 | final List images; 6 | final int index; 7 | final String? title; 8 | final IconButton Function(BuildContext, NcFile)? mainActionBuilder; 9 | 10 | ImageScreenArguments( 11 | this.images, 12 | this.index, { 13 | this.title, 14 | this.mainActionBuilder, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/model/route_args/navigatable_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | class NavigatableScreenArguments { 2 | final Uri uri; 3 | 4 | NavigatableScreenArguments({required this.uri}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/model/route_args/path_selector_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/model/route_args/navigatable_screen_arguments.dart'; 3 | 4 | class PathSelectorScreenArguments extends NavigatableScreenArguments { 5 | final void Function(Uri)? onSelect; 6 | final void Function(List, int)? onFileTap; 7 | final String? title; 8 | final bool fixedOrigin; 9 | final String schemeFilter; 10 | 11 | PathSelectorScreenArguments({ 12 | required Uri uri, 13 | this.onSelect, 14 | this.onFileTap, 15 | this.title, 16 | this.fixedOrigin = false, 17 | this.schemeFilter = "", 18 | }) : super(uri: uri); 19 | } 20 | -------------------------------------------------------------------------------- /lib/model/route_args/settings_screen_arguments.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:yaga/model/preferences/preference.dart'; 3 | 4 | class SettingsScreenArguments { 5 | RxCommand? onSettingChangedCommand; 6 | List preferences; 7 | void Function()? onCommit; 8 | void Function()? onCancel; 9 | 10 | SettingsScreenArguments({ 11 | required this.preferences, 12 | this.onSettingChangedCommand, 13 | this.onCommit, 14 | this.onCancel, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/model/sort_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | enum SortProperty { 4 | name, 5 | dateModified, 6 | } 7 | 8 | enum SortType { 9 | list, 10 | category, 11 | } 12 | 13 | class SortConfig extends Equatable { 14 | final SortType sortType; 15 | final SortProperty folderSortProperty; 16 | final SortProperty fileSortProperty; 17 | 18 | const SortConfig( 19 | this.sortType, this.fileSortProperty, this.folderSortProperty); 20 | 21 | @override 22 | List get props => [sortType, folderSortProperty, fileSortProperty]; 23 | } 24 | -------------------------------------------------------------------------------- /lib/model/sorted_category_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/model/sort_config.dart'; 3 | import 'package:yaga/model/sorted_file_list.dart'; 4 | 5 | class SortedCategoryList extends SortedFileList { 6 | @override 7 | final List folders; 8 | final List categories = []; 9 | final Map> categorizedFiles = {}; 10 | 11 | SortedCategoryList( 12 | SortConfig config, { 13 | required this.folders, 14 | }) : super(config); 15 | factory SortedCategoryList.empty(SortConfig config) => 16 | SortedCategoryList(config, folders: []); 17 | 18 | static String createKey(NcFile file) => 19 | file.lastModified.toString().split(" ")[0]; 20 | 21 | @override 22 | List get files => categorizedFiles.values.fold( 23 | [], 24 | (previousValue, element) => previousValue..addAll(element), 25 | ); 26 | 27 | @override 28 | bool remove(NcFile file) { 29 | bool removed = false; 30 | if (file.isDirectory) { 31 | removed = folders.remove(file); 32 | 33 | if (removed) { 34 | for (final catFiles in categorizedFiles.values) { 35 | catFiles.removeWhere( 36 | (f) => f.uri.path.startsWith(file.uri.path), 37 | ); 38 | } 39 | 40 | categorizedFiles.removeWhere((key, value) => value.isEmpty); 41 | } 42 | 43 | return removed; 44 | } 45 | 46 | final String key = createKey(file); 47 | 48 | if (categorizedFiles.containsKey(key)) { 49 | removed = categorizedFiles[key]!.remove(file); 50 | 51 | if (removed && categorizedFiles[key]!.isEmpty) { 52 | categories.remove(key); 53 | categorizedFiles.remove(key); 54 | } 55 | } 56 | 57 | return removed; 58 | } 59 | 60 | @override 61 | void removeAll() { 62 | categories.clear(); 63 | categorizedFiles.clear(); 64 | folders.clear(); 65 | } 66 | 67 | @override 68 | SortedFileList applyFilter( 69 | bool Function(NcFile p1) filter, 70 | ) { 71 | final filteredFolders = folders 72 | .where( 73 | (element) => filter(element), 74 | ) 75 | .toList(); 76 | 77 | final filtered = SortedCategoryList(config, folders: filteredFolders); 78 | 79 | for (final cat in categories) { 80 | final filteredCat = categorizedFiles[cat]! 81 | .where( 82 | (element) => filter(element), 83 | ) 84 | .toList(); 85 | if (filteredCat.isNotEmpty) { 86 | filtered.categories.add(cat); 87 | filtered.categorizedFiles[cat] = filteredCat; 88 | } 89 | } 90 | 91 | return filtered; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/model/sorted_file_folder_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/model/sort_config.dart'; 3 | import 'package:yaga/model/sorted_file_list.dart'; 4 | 5 | class SortedFileFolderList extends SortedFileList { 6 | @override 7 | final List files; 8 | @override 9 | final List folders; 10 | 11 | SortedFileFolderList( 12 | SortConfig config, 13 | this.files, 14 | this.folders, 15 | ) : super(config); 16 | 17 | factory SortedFileFolderList.empty( 18 | SortConfig config, 19 | ) => 20 | SortedFileFolderList(config, [], []); 21 | 22 | @override 23 | bool remove(NcFile file) { 24 | if (file.isDirectory) { 25 | final bool removed = folders.remove(file); 26 | 27 | if (removed) { 28 | files.removeWhere( 29 | (element) => element.uri.path.startsWith(file.uri.path), 30 | ); 31 | } 32 | 33 | return removed; 34 | } 35 | 36 | return files.remove(file); 37 | } 38 | 39 | @override 40 | void removeAll() { 41 | files.clear(); 42 | folders.clear(); 43 | } 44 | 45 | @override 46 | SortedFileList applyFilter( 47 | bool Function(NcFile p1) filter, 48 | ) { 49 | final filteredFiles = files 50 | .where( 51 | (element) => filter(element), 52 | ) 53 | .toList(); 54 | final filteredFolders = folders 55 | .where( 56 | (element) => filter(element), 57 | ) 58 | .toList(); 59 | return SortedFileFolderList(config, filteredFiles, filteredFolders); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/model/sorted_file_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/model/sort_config.dart'; 3 | 4 | abstract class SortedFileList> { 5 | final SortConfig config; 6 | 7 | SortedFileList(this.config); 8 | 9 | List get files; 10 | List get folders; 11 | 12 | /// Removes give file or folder from the collection. 13 | /// If it is a folder then also all files belonging to that folder are removed. 14 | bool remove(NcFile file); 15 | 16 | void removeAll(); 17 | 18 | SortedFileList applyFilter(bool Function(NcFile) filter); 19 | 20 | int get length => files.length + folders.length; 21 | } 22 | -------------------------------------------------------------------------------- /lib/model/sync_file.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | 3 | class SyncFile { 4 | NcFile file; 5 | bool remote; 6 | 7 | SyncFile(this.file, {this.remote = false}); 8 | } 9 | -------------------------------------------------------------------------------- /lib/model/system_location.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:yaga/utils/uri_utils.dart'; 4 | 5 | class SystemLocation { 6 | final String privatePath; 7 | final String publicPath; 8 | final Uri absoluteUri; 9 | final Uri origin; 10 | 11 | SystemLocation(Directory directory, this.origin,) : privatePath = directory.path, publicPath = "", absoluteUri = directory.uri; 12 | 13 | SystemLocation.fromSplitter(Directory directory, this.origin, String splitter) 14 | : privatePath = directory.path.split(splitter)[0], 15 | publicPath = splitter + directory.path.split(splitter)[1], 16 | absoluteUri = directory.uri; 17 | 18 | Uri get uri => fromUri(uri: origin, path: publicPath); 19 | } 20 | -------------------------------------------------------------------------------- /lib/services/file_provider_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/services/service.dart'; 3 | 4 | abstract class FileProviderService> 5 | extends Service { 6 | Stream list(Uri dir, bool favorites) => const Stream.empty(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/services/intent_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:mime/mime.dart'; 5 | import 'package:yaga/model/nc_file.dart'; 6 | import 'package:yaga/services/service.dart'; 7 | 8 | class IntentService extends Service { 9 | static const _intentChannel = MethodChannel('yaga.channel.intent'); 10 | late String _intentAction; 11 | 12 | @override 13 | Future init() async { 14 | _intentAction = Platform.isAndroid ? await getIntentAction() : "linux"; 15 | return this; 16 | } 17 | 18 | String getCachedIntentAction() => _intentAction; 19 | 20 | Future getIntentAction() async { 21 | return _intentChannel 22 | .invokeMethod("getIntentAction") 23 | .then((value) => value.toString()); 24 | } 25 | 26 | Future setSelectedFile(NcFile file) async { 27 | //todo: maybe we should keep the mime type in the NcFile object 28 | final String mime = lookupMimeType(file.localFile!.file.path)??''; 29 | return _intentChannel.invokeMethod("setSelectedFile", { 30 | "path": file.localFile!.file.path, 31 | "mime": mime, 32 | }).then((value) => value as bool); 33 | } 34 | 35 | Future attachData(NcFile file) async { 36 | final String mime = lookupMimeType(file.localFile!.file.path)??''; 37 | return _intentChannel.invokeMethod("attachData", { 38 | "path": file.localFile!.file.path, 39 | "mime": mime, 40 | }); 41 | } 42 | 43 | bool get isOpenForSelect => 44 | _intentAction == "android.intent.action.GET_CONTENT" || 45 | _intentAction == "android.intent.action.PICK"; 46 | } 47 | -------------------------------------------------------------------------------- /lib/services/name_exchange_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/services/media_file_service.dart'; 2 | import 'package:yaga/services/service.dart'; 3 | import 'package:yaga/services/uri_name_resolver.dart'; 4 | 5 | class NameExchangeService extends Service { 6 | 7 | Map resolvers = {}; 8 | 9 | NameExchangeService(MediaFileService mediaFileService) { 10 | resolvers[mediaFileService.scheme] = mediaFileService; 11 | } 12 | 13 | Uri convertUriToHumanReadableUri(Uri uri) { 14 | if(resolvers.containsKey(uri.scheme)) { 15 | return resolvers[uri.scheme]!.getHumanReadableForm(uri); 16 | } 17 | return uri; 18 | } 19 | } -------------------------------------------------------------------------------- /lib/services/secure_storage_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 3 | import 'package:yaga/services/service.dart'; 4 | 5 | class SecureStorageService extends Service { 6 | final FlutterSecureStorage _storage = const FlutterSecureStorage(); 7 | 8 | Future savePreference(String key, String value) { 9 | return _storage.write(key: key, value: value).catchError(_logAndRethrow); 10 | } 11 | 12 | Future loadPreference(String key) { 13 | return _storage 14 | .read(key: key) 15 | .catchError(_logAndRethrow) 16 | .then((value) => value ?? ""); 17 | } 18 | 19 | Future deletePreference(String key) { 20 | return _storage.delete(key: key).catchError(_logAndRethrow); 21 | } 22 | 23 | void _logAndRethrow(Object err) { 24 | if (err is PlatformException) { 25 | logger.severe("SecureStorage: ${err.code}"); 26 | logger.severe("SecureStorage: ${err.message}"); 27 | logger.severe("SecureStorage: ${err.details}"); 28 | logger.severe("SecureStorage: ${err.stacktrace}"); 29 | } else { 30 | logger.severe(err); 31 | } 32 | throw err; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/services/service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:yaga/utils/logger.dart'; 3 | 4 | mixin class Service> { 5 | @protected 6 | final logger = YagaLogger.getLogger(T); 7 | 8 | Future init() async => this as T; 9 | } 10 | -------------------------------------------------------------------------------- /lib/services/shared_preferences_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import 'package:yaga/model/preferences/preference.dart'; 3 | import 'package:yaga/model/preferences/serializable_preference.dart'; 4 | import 'package:yaga/services/service.dart'; 5 | 6 | class SharedPreferencesService extends Service { 7 | late SharedPreferences _instance; 8 | 9 | @override 10 | Future init() async { 11 | _instance = await SharedPreferences.getInstance(); 12 | return this; 13 | } 14 | 15 | Future removePreference(Preference pref) => _instance.remove(pref.key!); 16 | 17 | Future savePreferenceToString( 18 | SerializablePreference pref) => 19 | _instance.setString(pref.key!, pref.serialize()); 20 | 21 | P loadPreferenceFromString< 22 | P extends SerializablePreference>(P pref) => 23 | pref.deserialize(_instance.getString(pref.key!)) as P; 24 | 25 | Future savePreferenceToBool( 26 | SerializablePreference pref) => 27 | _instance.setBool(pref.key!, pref.serialize()); 28 | 29 | P loadPreferenceFromBool< 30 | P extends SerializablePreference>(P pref) => 31 | pref.deserialize(_instance.getBool(pref.key!)) as P; 32 | 33 | Future savePreferenceToInt( 34 | SerializablePreference pref) => 35 | _instance.setInt(pref.key!, pref.serialize()); 36 | 37 | P loadPreferenceFromInt< 38 | P extends SerializablePreference>(P pref) => 39 | pref.deserialize(_instance.getInt(pref.key!)) as P; 40 | 41 | Future savePreferenceToStringList( 42 | SerializablePreference, dynamic, dynamic> pref) => 43 | _instance.setStringList(pref.key!, pref.serialize()); 44 | 45 | P loadPreferenceFromStringList< 46 | P extends SerializablePreference, dynamic, dynamic>>( 47 | P pref) => 48 | pref.deserialize(_instance.getStringList(pref.key!)) as P; 49 | } 50 | -------------------------------------------------------------------------------- /lib/services/uri_name_resolver.dart: -------------------------------------------------------------------------------- 1 | abstract class UriNameResolver { 2 | String get scheme; 3 | Uri getHumanReadableForm(Uri uri); 4 | } 5 | -------------------------------------------------------------------------------- /lib/utils/background_worker/background_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_background_service_android/flutter_background_service_android.dart'; 2 | import 'package:yaga/utils/background_worker/background_commands.dart'; 3 | import 'package:yaga/utils/background_worker/json_convertable.dart'; 4 | 5 | class BackgroundChannel{ 6 | final AndroidServiceInstance service; 7 | 8 | BackgroundChannel(this.service); 9 | 10 | void send(JsonConvertable msg) => service.invoke(BackgroundCommands.workerToMain, msg.toJson()); 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/background_worker/background_commands.dart: -------------------------------------------------------------------------------- 1 | class BackgroundCommands { 2 | static const String started = "started"; 3 | static const String init = "init"; 4 | static const String initDone = "initDone"; 5 | static const String stopped = "stopped"; 6 | static const String mainToWorker = "mainToWorker"; 7 | static const String workerToMain = "workerToMain"; 8 | } -------------------------------------------------------------------------------- /lib/utils/background_worker/json_convertable.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 2 | 3 | abstract class JsonConvertable extends Message { 4 | static const String _jsonKey = "key"; 5 | static const String jsonTypeField = "jsonType"; 6 | 7 | final String jsonType; 8 | 9 | JsonConvertable(String key, this.jsonType) : super(key); 10 | 11 | JsonConvertable.fromJson(Map json) 12 | : jsonType = json[jsonTypeField] as String, 13 | super(json[_jsonKey] as String); 14 | 15 | Map toJson() { 16 | return { 17 | jsonTypeField: jsonType, 18 | _jsonKey: key, 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/background_worker/messages/background_downloaded_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/single_file_message.dart'; 3 | 4 | class BackgroundDownloadedRequest extends SingleFileMessage { 5 | static const String jsonTypeConst = "BackgroundDownloadedRequest"; 6 | static const String _jsonSuccess = "success"; 7 | 8 | final bool success; 9 | 10 | BackgroundDownloadedRequest({required NcFile file, required this.success}) 11 | : super(jsonTypeConst, jsonTypeConst, file); 12 | 13 | BackgroundDownloadedRequest.fromJson(Map json) 14 | : success = json[_jsonSuccess] as bool, 15 | super.fromJson(json); 16 | 17 | @override 18 | Map toJson() { 19 | final map = super.toJson(); 20 | map[_jsonSuccess] = success; 21 | return map; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/background_worker/messages/background_init_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_login_data.dart'; 2 | 3 | class BackgroundInitMsg { 4 | static const String _jsonLastLoginData = "lastLoginData"; 5 | static const String _jsonFingerprint = "fingerprint"; 6 | 7 | final NextCloudLoginData lastLoginData; 8 | final String fingerprint; 9 | 10 | BackgroundInitMsg(this.lastLoginData, this.fingerprint); 11 | 12 | BackgroundInitMsg.fromJson(Map json) 13 | : lastLoginData = NextCloudLoginData.fromJson( 14 | json[_jsonLastLoginData] as Map, 15 | ), 16 | fingerprint = json[_jsonFingerprint] as String; 17 | 18 | Map toJson() { 19 | return { 20 | _jsonLastLoginData: lastLoginData.toJson(), 21 | _jsonFingerprint: fingerprint, 22 | }; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/utils/background_worker/work_tracker.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/background_worker/json_convertable.dart'; 2 | 3 | class WorkTracker { 4 | final Map activeTasks = {}; 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/download_file_image.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:ui' as ui show Codec, ImmutableBuffer; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/rendering.dart'; 6 | import 'package:yaga/model/fetched_file.dart'; 7 | 8 | typedef _SimpleDecoderCallback = Future Function(ui.ImmutableBuffer buffer); 9 | 10 | // this entire class is a copy of the respective FileImage functions 11 | // with the addition of an await for the localFileAvailable 12 | // proper solution would probably be to write an own ImageProvider 13 | class DownloadFileImage extends FileImage { 14 | final Future localFileAvailable; 15 | 16 | const DownloadFileImage(File file, this.localFileAvailable) : super(file); 17 | 18 | /// this function is copied from parent without changes 19 | @override 20 | ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) { 21 | return MultiFrameImageStreamCompleter( 22 | codec: _loadAsync(key, decode: decode), 23 | scale: key.scale, 24 | debugLabel: key.file.path, 25 | informationCollector: () => [ 26 | ErrorDescription('Path: ${file.path}'), 27 | ], 28 | ); 29 | } 30 | 31 | @override 32 | @protected 33 | ImageStreamCompleter loadImage(FileImage key, ImageDecoderCallback decode) { 34 | return MultiFrameImageStreamCompleter( 35 | codec: _loadAsync(key, decode: decode), 36 | scale: key.scale, 37 | debugLabel: key.file.path, 38 | informationCollector: () => [ 39 | ErrorDescription('Path: ${file.path}'), 40 | ], 41 | ); 42 | } 43 | 44 | /// this function is copied from the parent 45 | /// await localFileAvailable was added and Uint8List result is reused 46 | Future _loadAsync( 47 | FileImage key, { 48 | required _SimpleDecoderCallback decode, 49 | }) async { 50 | final fetchedFile = await localFileAvailable; 51 | assert(key == this); 52 | // TODO(jonahwilliams): making this sync caused test failures that seem to 53 | // indicate that we can fail to call evict unless at least one await has 54 | // occurred in the test. 55 | // https://github.com/flutter/flutter/issues/113044 56 | final int lengthInBytes = fetchedFile.data.length; 57 | if (lengthInBytes == 0) { 58 | // The file may become available later. 59 | PaintingBinding.instance.imageCache.evict(key); 60 | throw StateError('$file is empty and cannot be loaded as an image.'); 61 | } 62 | return decode(await ui.ImmutableBuffer.fromUint8List(fetchedFile.data)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/bridges/file_manager_bridge.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | import 'package:yaga/managers/file_manager/file_manager.dart'; 4 | import 'package:yaga/managers/file_service_manager/media_file_manager.dart'; 5 | import 'package:yaga/utils/background_worker/background_worker.dart'; 6 | import 'package:yaga/utils/forground_worker/foreground_worker.dart'; 7 | import 'package:yaga/utils/forground_worker/messages/image_update_msg.dart'; 8 | 9 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 10 | 11 | //todo: slowly deprecate bridges; this is logic which belongs into the fileManager 12 | class FileManagerBridge { 13 | final FileManager _fileManager; 14 | final ForegroundWorker _worker; 15 | final MediaFileManager _mediaFileManager; 16 | final BackgroundWorker _backgroundWorker; 17 | 18 | FileManagerBridge( 19 | this._fileManager, 20 | this._worker, 21 | this._mediaFileManager, 22 | this._backgroundWorker, 23 | ) { 24 | _registerWorkerMessageWithTransformation( 25 | (ImageUpdateMsg msg) => msg.file, 26 | _fileManager.updateImageCommand, 27 | ); 28 | _registerWorkerMessage(_fileManager.fetchedFileCommand); 29 | _registerWorkerMessage(_fileManager.filesActionDoneCommand); 30 | _registerWorkerMessage(_fileManager.updateFilesCommand); 31 | _registerWorkerMessage(_fileManager.fileUpdateMessage); 32 | 33 | _fileManager.fetchFileListCommand 34 | .where((event) => !_mediaFileManager.isRelevant(event.uri.scheme)) 35 | .listen((event) => _worker.sendRequest(event)); 36 | 37 | _fileManager.sortFilesListCommand 38 | .listen((value) => _worker.sendRequest(value)); 39 | } 40 | 41 | _registerWorkerMessageWithTransformation( 42 | P Function(T) transformation, RxCommand command) { 43 | _worker.isolateResponseCommand 44 | .mergeWith([_backgroundWorker.isolateResponseCommand]) 45 | .where((event) => event is T) 46 | .map((event) => event as T) 47 | .map(transformation) 48 | .listen((msg) => command(msg)); 49 | } 50 | 51 | _registerWorkerMessage(RxCommand command) { 52 | _registerWorkerMessageWithTransformation((T msg) => msg, command); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/bridges/nextcloud_manager_bridge.dart: -------------------------------------------------------------------------------- 1 | import 'package:rx_command/rx_command.dart'; 2 | import 'package:yaga/managers/file_service_manager/isolateable/nextcloud_file_manger.dart'; 3 | import 'package:yaga/managers/nextcloud_manager.dart'; 4 | import 'package:yaga/model/nc_file.dart'; 5 | import 'package:yaga/utils/forground_worker/foreground_worker.dart'; 6 | import 'package:yaga/utils/forground_worker/messages/download_preview_complete.dart'; 7 | import 'package:yaga/utils/forground_worker/messages/download_preview_request.dart'; 8 | import 'package:yaga/utils/forground_worker/messages/login_state_msg.dart'; 9 | 10 | class NextcloudManagerBridge { 11 | final NextCloudManager _nextCloudManager; 12 | final ForegroundWorker _worker; 13 | final NextcloudFileManager _nextcloudFileManager; 14 | 15 | RxCommand downloadPreviewCommand = 16 | RxCommand.createSync((param) => param); 17 | 18 | NextcloudManagerBridge( 19 | this._nextCloudManager, 20 | this._worker, 21 | this._nextcloudFileManager, 22 | ) { 23 | //todo: update loginStateCommand has no logout values... see todo in ncManager 24 | _nextCloudManager.updateLoginStateCommand.listen((value) { 25 | _worker.sendRequest(LoginStateMsg("", value)); 26 | }); 27 | 28 | _worker.isolateResponseCommand 29 | .where((event) => event is DownloadPreviewComplete) 30 | .map((event) => event as DownloadPreviewComplete) 31 | .listen( 32 | (value) => value.success 33 | ? _nextcloudFileManager.updatePreviewCommand(value.file) 34 | : _nextcloudFileManager.downloadPreviewFaildCommand(value.file), 35 | ); 36 | 37 | downloadPreviewCommand.listen((ncFile) { 38 | _worker.sendRequest(DownloadPreviewRequest("", ncFile)); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/bridges/settings_manager_bridge.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/rxdart.dart'; 2 | import 'package:yaga/managers/settings_manager.dart'; 3 | import 'package:yaga/utils/forground_worker/foreground_worker.dart'; 4 | import 'package:yaga/utils/forground_worker/messages/preference_msg.dart'; 5 | import 'package:yaga/utils/logger.dart'; 6 | 7 | class SettingsManagerBridge { 8 | final _logger = YagaLogger.getLogger(SettingsManagerBridge); 9 | final SettingsManager _settingsManager; 10 | final ForegroundWorker _worker; 11 | 12 | SettingsManagerBridge(this._settingsManager, this._worker); 13 | 14 | Future init() async { 15 | _settingsManager.updateSettingCommand.doOnData((event) { 16 | _logger.warning(event); 17 | }).listen((event) => _worker.sendRequest(PreferenceMsg("", event))); 18 | 19 | return this; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/handlers/user_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:isolate'; 3 | 4 | import 'package:yaga/managers/isolateable/isolated_settings_manager.dart'; 5 | import 'package:yaga/services/isolateable/nextcloud_service.dart'; 6 | import 'package:yaga/utils/forground_worker/isolate_handler_regestry.dart'; 7 | import 'package:yaga/utils/forground_worker/isolate_msg_handler.dart'; 8 | import 'package:yaga/utils/forground_worker/messages/init_msg.dart'; 9 | import 'package:yaga/utils/forground_worker/messages/login_state_msg.dart'; 10 | import 'package:yaga/utils/forground_worker/messages/preference_msg.dart'; 11 | import 'package:yaga/utils/self_signed_cert_handler.dart'; 12 | import 'package:yaga/utils/service_locator.dart'; 13 | 14 | class UserHandler implements IsolateMsgHandler { 15 | @override 16 | Future initIsolated(InitMsg init, SendPort isolateToMain, 17 | IsolateHandlerRegistry registry) async { 18 | registry 19 | .registerHandler((msg) => handleLoginStateChanged(msg)); 20 | registry.registerHandler( 21 | (msg) => getIt 22 | .get() 23 | .updateSettingCommand(msg.preference), 24 | ); 25 | return this; 26 | } 27 | 28 | void handleLoginStateChanged(LoginStateMsg message) { 29 | final NextCloudService ncService = getIt.get(); 30 | 31 | if (message.loginData.server == null) { 32 | getIt.get().revokeCert(); 33 | return ncService.logout(); 34 | } 35 | 36 | ncService.login(message.loginData); 37 | return; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/isolate_handler_regestry.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 2 | import 'package:yaga/utils/logger.dart'; 3 | 4 | class IsolateHandlerRegistry { 5 | final logger = YagaLogger.getLogger(IsolateHandlerRegistry); 6 | final Map> handlers = {}; 7 | 8 | void registerHandler(Function(M) handler) { 9 | handlers.putIfAbsent(M, () => []); 10 | handlers[M]!.add((Message msg) => handler(msg as M)); 11 | } 12 | 13 | void handleMessage(Message msg) { 14 | if (handlers.containsKey(msg.runtimeType)) { 15 | for (final handler in handlers[msg.runtimeType]!) { 16 | handler(msg); 17 | } 18 | } else { 19 | logger.shout("No handler registered for ${msg.runtimeType}"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/isolate_msg_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | 3 | import 'package:yaga/utils/forground_worker/isolate_handler_regestry.dart'; 4 | import 'package:yaga/utils/forground_worker/messages/init_msg.dart'; 5 | 6 | abstract class IsolateMsgHandler> { 7 | Future initIsolated( 8 | InitMsg init, 9 | SendPort isolateToMain, 10 | IsolateHandlerRegistry registry, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/isolateable.dart: -------------------------------------------------------------------------------- 1 | import 'dart:isolate'; 2 | 3 | import 'package:yaga/utils/forground_worker/messages/init_msg.dart'; 4 | 5 | mixin Isolateable> { 6 | Future initIsolated(InitMsg init, SendPort isolateToMain) async => this as T; 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/download_file_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/single_file_message.dart'; 3 | 4 | class DownloadFileRequest extends SingleFileMessage { 5 | static const String jsonTypeConst = "DownloadFileRequest"; 6 | static const String _jsonForceDownload = "forceDownload"; 7 | static const String _jsonPersist = "persist"; 8 | 9 | bool forceDownload; 10 | bool persist; 11 | 12 | DownloadFileRequest( 13 | NcFile file, { 14 | this.forceDownload = false, 15 | this.persist = false, 16 | }) : super(jsonTypeConst, jsonTypeConst, file); 17 | 18 | DownloadFileRequest.fromJson(Map json) 19 | : forceDownload = json[_jsonForceDownload] as bool, 20 | persist = json[_jsonPersist] as bool, 21 | super.fromJson(json); 22 | 23 | @override 24 | Map toJson() { 25 | final superMap = super.toJson(); 26 | superMap[_jsonForceDownload] = forceDownload; 27 | superMap[_jsonPersist] = persist; 28 | return superMap; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/download_preview_complete.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class DownloadPreviewComplete extends Message { 5 | final NcFile file; 6 | final bool success; 7 | 8 | DownloadPreviewComplete( 9 | String key, 10 | this.file, { 11 | this.success = true, 12 | }) : super(key); 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/download_preview_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class DownloadPreviewRequest extends Message { 5 | final NcFile file; 6 | 7 | DownloadPreviewRequest(String key, this.file) : super(key); 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/file_list_done.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/forground_worker/messages/file_list_message.dart'; 2 | 3 | class FileListDone extends FileListMessage { 4 | FileListDone(String key, Uri uri, {bool recursive = false}) 5 | : super(key, uri, recursive: recursive); 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/file_list_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 2 | 3 | abstract class FileListMessage extends Message { 4 | final Uri uri; 5 | final bool recursive; 6 | FileListMessage(String key, this.uri, {this.recursive = false}) : super(key); 7 | } 8 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/file_list_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/sort_config.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class FileListRequest extends Message { 5 | final Uri uri; 6 | final bool recursive; 7 | final bool favorites; 8 | final SortConfig config; 9 | 10 | FileListRequest( 11 | String key, 12 | this.uri, 13 | this.config, { 14 | this.recursive = false, 15 | this.favorites = false, 16 | }) : super(key); 17 | } 18 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/file_list_response.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/sorted_file_list.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/file_list_message.dart'; 3 | 4 | class FileListResponse extends FileListMessage { 5 | final SortedFileList files; 6 | 7 | FileListResponse(String key, Uri uri, this.files, {bool recursive = false}) 8 | : super(key, uri, recursive: recursive); 9 | } 10 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/file_update_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/single_file_message.dart'; 3 | 4 | class FileUpdateMsg extends SingleFileMessage { 5 | static const String jsonTypeConst = "FileUpdateMsg"; 6 | 7 | FileUpdateMsg(String key, NcFile file) : super(key, jsonTypeConst, file); 8 | FileUpdateMsg.fromJson(Map json) : super.fromJson(json); 9 | } 10 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/files_action/delete_files_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/files_action/files_action_request.dart'; 3 | 4 | class DeleteFilesRequest extends FilesActionRequest { 5 | static const String jsonTypeConst = "DeleteFilesRequest"; 6 | static const String _jsonLocal = "local"; 7 | final bool local; 8 | 9 | DeleteFilesRequest({required super.key, required super.files, required super.sourceDir, required this.local}) 10 | : super(jsonType: jsonTypeConst); 11 | 12 | DeleteFilesRequest.fromJson(super.json) 13 | : local = json[_jsonLocal] as bool, 14 | super.fromJson(); 15 | 16 | @override 17 | Map toJson() { 18 | return super.toJson() 19 | ..[_jsonLocal] = local; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/files_action/destination_action_files_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/files_action/files_action_request.dart'; 3 | 4 | enum DestinationAction { copy, move } 5 | 6 | class DestinationActionFilesRequest extends FilesActionRequest { 7 | static const String jsonTypeConst = "DestinationActionFilesRequest"; 8 | static const String _jsonDestination = "destination"; 9 | static const String _jsonAction = "action"; 10 | static const String _jsonOverwrite = "overwrite"; 11 | 12 | final Uri destination; 13 | final DestinationAction action; 14 | final bool overwrite; 15 | 16 | DestinationActionFilesRequest({ 17 | required super.key, 18 | required super.files, 19 | required this.destination, 20 | required super.sourceDir, 21 | this.action = DestinationAction.copy, 22 | this.overwrite = false, 23 | }) : super(jsonType: jsonTypeConst); 24 | 25 | DestinationActionFilesRequest.fromJson(super.json) 26 | : destination = Uri.parse(json[_jsonDestination] as String), 27 | action = DestinationAction.values.firstWhere((element) => (json[_jsonAction] as String) == element.name), 28 | overwrite = json[_jsonOverwrite] as bool, 29 | super.fromJson(); 30 | 31 | @override 32 | Map toJson() { 33 | final map = super.toJson(); 34 | map[_jsonDestination] = destination.toString(); 35 | map[_jsonAction] = action.name; 36 | map[_jsonOverwrite] = overwrite; 37 | return map; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/files_action/favorite_files_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/files_action/files_action_request.dart'; 3 | 4 | class FavoriteFilesRequest extends FilesActionRequest { 5 | static const String jsonTypeConst = "FavoriteFilesRequest"; 6 | 7 | FavoriteFilesRequest({required super.key, required super.files, required super.sourceDir}) 8 | : super(jsonType: jsonTypeConst); 9 | 10 | FavoriteFilesRequest.fromJson(super.json) : super.fromJson(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/files_action/files_action_done.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/background_worker/json_convertable.dart'; 2 | 3 | class FilesActionDone extends JsonConvertable { 4 | static const String jsonTypeConst = "FilesActionDone"; 5 | static const String _jsonDestination = "destination"; 6 | final Uri destination; 7 | 8 | //todo: background: not sure adding destination was the best solution; it only matters for actionDone follow up 9 | FilesActionDone(String key, this.destination) : super(key, jsonTypeConst); 10 | 11 | FilesActionDone.fromJson(Map json) 12 | : destination = Uri.parse(json[_jsonDestination] as String), 13 | super.fromJson(json); 14 | 15 | @override 16 | Map toJson() { 17 | return super.toJson()..[_jsonDestination] = destination.toString(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/files_action/files_action_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/background_worker/json_convertable.dart'; 3 | 4 | abstract class FilesActionRequest extends JsonConvertable { 5 | static const String _jsonSourceDir = "sourceDir"; 6 | final Uri sourceDir; 7 | static const String _jsonFiles = "files"; 8 | final List files; 9 | 10 | FilesActionRequest({ 11 | required String key, 12 | required String jsonType, 13 | required this.sourceDir, 14 | required this.files, 15 | }) : super(key, jsonType); 16 | 17 | FilesActionRequest.fromJson(super.json) 18 | : sourceDir = Uri.parse(json[_jsonSourceDir] as String), 19 | files = (json[_jsonFiles] as List).map((e) => NcFile.fromJson(e as Map)).toList(), 20 | super.fromJson(); 21 | 22 | @override 23 | Map toJson() { 24 | final map = super.toJson(); 25 | map[_jsonSourceDir] = sourceDir.toString(); 26 | map[_jsonFiles] = files.map((e) => e.toJson()).toList(); 27 | return map; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/flush_logs_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 2 | 3 | class FlushLogsMessage extends Message { 4 | final bool flushed; 5 | FlushLogsMessage({this.flushed = false}) : super("flush-log"); 6 | } 7 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/image_update_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/single_file_message.dart'; 3 | 4 | class ImageUpdateMsg extends SingleFileMessage { 5 | static const String jsonTypeConst = "ImageUpdateMsg"; 6 | 7 | ImageUpdateMsg(String key, NcFile file) : super(key, jsonTypeConst, file); 8 | ImageUpdateMsg.fromJson(Map json) : super.fromJson(json); 9 | } 10 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/init_msg.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:isolate'; 3 | 4 | import 'package:yaga/model/nc_login_data.dart'; 5 | import 'package:yaga/model/preferences/bool_preference.dart'; 6 | import 'package:yaga/model/preferences/mapping_preference.dart'; 7 | import 'package:yaga/utils/background_worker/messages/background_init_msg.dart'; 8 | 9 | class InitMsg extends BackgroundInitMsg { 10 | final SendPort sendPort; 11 | final Directory externalPath; 12 | final Directory tmpPath; 13 | final List externalPaths; 14 | final MappingPreference? mapping; 15 | final BoolPreference autoPersist; 16 | 17 | InitMsg( 18 | this.sendPort, 19 | this.externalPath, 20 | this.tmpPath, 21 | this.externalPaths, 22 | NextCloudLoginData lastLoginData, 23 | this.mapping, 24 | String fingerprint, 25 | this.autoPersist, 26 | ): super(lastLoginData, fingerprint); 27 | } 28 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/login_state_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_login_data.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class LoginStateMsg extends Message { 5 | final NextCloudLoginData loginData; 6 | 7 | LoginStateMsg(String key, this.loginData) : super(key); 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/merge_sort_done.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/sorted_file_list.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class MergeSortDone extends Message { 5 | final SortedFileList sorted; 6 | final bool updateLoading; 7 | 8 | MergeSortDone( 9 | String key, 10 | this.sorted, { 11 | this.updateLoading = false, 12 | }) : super(key); 13 | } 14 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/merge_sort_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/sorted_file_list.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class MergeSortRequest extends Message { 5 | final SortedFileList main; 6 | final SortedFileList addition; 7 | final Uri? uri; 8 | final bool recursive; 9 | final bool updateLoading; 10 | 11 | MergeSortRequest( 12 | String key, 13 | this.main, 14 | this.addition, { 15 | this.uri, 16 | this.recursive = false, 17 | this.updateLoading = false, 18 | }) : super(key); 19 | } 20 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/message.dart: -------------------------------------------------------------------------------- 1 | abstract class Message { 2 | final String key; 3 | 4 | Message(this.key); 5 | } 6 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/preference_msg.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/preferences/preference.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 3 | 4 | class PreferenceMsg extends Message { 5 | final Preference preference; 6 | 7 | PreferenceMsg(String key, this.preference) : super(key); 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/single_file_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/background_worker/json_convertable.dart'; 3 | 4 | abstract class SingleFileMessage extends JsonConvertable { 5 | static const String _jsonFile = "file"; 6 | 7 | final NcFile file; 8 | 9 | SingleFileMessage(String key, String type, this.file) : super(key, type); 10 | 11 | SingleFileMessage.fromJson(Map json) 12 | : file = NcFile.fromJson(json[_jsonFile] as Map), 13 | super.fromJson(json); 14 | 15 | @override 16 | Map toJson() { 17 | final superMap = super.toJson(); 18 | superMap[_jsonFile] = file.toJson(); 19 | return superMap; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/utils/forground_worker/messages/sort_request.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:yaga/utils/forground_worker/messages/file_list_request.dart'; 3 | import 'package:yaga/utils/forground_worker/messages/message.dart'; 4 | 5 | class SortRequest extends Message { 6 | final List files; 7 | final FileListRequest fileListRequest; 8 | 9 | SortRequest(String key, this.files, this.fileListRequest) : super(key); 10 | } 11 | -------------------------------------------------------------------------------- /lib/utils/navigation/yaga_route_information_parser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class YagaRouteInformationParser extends RouteInformationParser { 4 | @override 5 | Future parseRouteInformation(RouteInformation routeInformation) async { 6 | return Uri.parse(routeInformation.location??''); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/utils/navigation/yaga_router_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/navigation_manager.dart'; 3 | import 'package:yaga/managers/tab_manager.dart'; 4 | import 'package:yaga/model/route_args/directory_navigation_screen_arguments.dart'; 5 | import 'package:yaga/services/intent_service.dart'; 6 | import 'package:yaga/utils/service_locator.dart'; 7 | import 'package:yaga/utils/navigation/yaga_router.dart'; 8 | import 'package:yaga/views/screens/directory_traversal_screen.dart'; 9 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 10 | 11 | class YagaRouterDelegate extends RouterDelegate 12 | with ChangeNotifier, PopNavigatorRouterDelegateMixin { 13 | @override 14 | final GlobalKey navigatorKey = GlobalKey(); 15 | final NavigationManager _navigationManager = getIt.get(); 16 | IntentService intentService = getIt.get(); 17 | 18 | YagaRouterDelegate() { 19 | _navigationManager.showDirectoryNavigation.listen((value) { 20 | notifyListeners(); 21 | }); 22 | 23 | getIt 24 | .get() 25 | .tabChangedCommand 26 | .listen((value) => _navigationManager.showDirectoryNavigation(null)); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Navigator( 32 | key: navigatorKey, 33 | onGenerateRoute: generateRoute, 34 | pages: [getInitialPage(), ..._buildDirectoryNavigationPage()], 35 | onPopPage: (route, result) { 36 | if (!route.didPop(result)) { 37 | return false; 38 | } 39 | 40 | _navigationManager.showDirectoryNavigation(null); 41 | 42 | return true; 43 | }, 44 | ); 45 | } 46 | 47 | @override 48 | Future setNewRoutePath(Uri configuration) async { 49 | //todo: we are not yet handling roots from the system 50 | } 51 | 52 | List _buildDirectoryNavigationPage() { 53 | final DirectoryNavigationScreenArguments? args = 54 | _navigationManager.showDirectoryNavigation.lastResult; 55 | 56 | if (args == null) { 57 | return []; 58 | } 59 | 60 | return [ 61 | MaterialPage( 62 | key: ValueKey(args.uri.toString()), 63 | child: DirectoryTraversalScreen(args), 64 | ) 65 | ]; 66 | } 67 | 68 | Page getInitialPage() { 69 | return MaterialPage( 70 | key: const ValueKey(YagaHomeScreen.route), 71 | child: YagaHomeScreen(), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/utils/ncfile_stream_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/nc_file.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | 4 | extension NcFileStreamExtensions on Stream { 5 | Stream> collectToList() { 6 | return toList().asStream().onErrorReturn([]); 7 | } 8 | 9 | Stream recursively(Stream Function(Uri, {bool favorites}) listFilesFromUpstream, 10 | {required bool recursive, bool favorites = false}) { 11 | return flatMap( 12 | (file) => Rx.merge([ 13 | Stream.value(file), 14 | Stream.value(file) 15 | .where((file) => file.isDirectory) 16 | .where((_) => recursive) 17 | .flatMap( 18 | (file) => listFilesFromUpstream( 19 | file.uri, 20 | favorites: favorites, 21 | ).recursively( 22 | listFilesFromUpstream, 23 | recursive: recursive, 24 | favorites: favorites, 25 | ), 26 | ) 27 | ]), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/utils/nextcloud_client_factory.dart: -------------------------------------------------------------------------------- 1 | import 'package:nextcloud/nextcloud.dart'; 2 | import 'package:yaga/utils/self_signed_cert_handler.dart'; 3 | 4 | class NextCloudClientFactory { 5 | // we do not actually need the SelfSignedCertHandler, 6 | // however we have to make sure it was initialized 7 | // before allowing anyone to create Nextcloud clients 8 | // ignore: avoid_unused_constructor_parameters 9 | NextCloudClientFactory(SelfSignedCertHandler handler); 10 | 11 | //todo: is this really the right place for this? 12 | String get userAgent => "Nextcloud Yaga"; 13 | 14 | NextcloudClient createNextCloudClient( 15 | Uri host, 16 | String username, 17 | String password, 18 | ) => 19 | NextcloudClient( 20 | host, 21 | loginName: username, 22 | password: password, 23 | userAgentOverride: userAgent, 24 | 25 | ); 26 | 27 | NextcloudClient createUnauthenticatedClient(Uri host) => 28 | NextcloudClient( 29 | host, 30 | userAgentOverride: userAgent, 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /lib/utils/nextcloud_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | 3 | class NextcloudColors { 4 | NextcloudColors._(); 5 | 6 | static const Color darkBlue = Color(0xFF0082C9); 7 | 8 | static const Color lightBlue = Color(0xFF1CAFFF); 9 | } 10 | -------------------------------------------------------------------------------- /lib/utils/uri_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:validators/sanitizers.dart'; 2 | import 'package:yaga/model/nc_file.dart'; 3 | 4 | //todo: refactor into non static functions in util class UriUtils 5 | 6 | Uri fromUri({ 7 | required Uri uri, 8 | String? scheme, 9 | String? userInfo, 10 | String? host, 11 | int? port, 12 | String? path, 13 | }) => 14 | Uri( 15 | scheme: scheme ?? uri.scheme, 16 | userInfo: userInfo ?? uri.userInfo, 17 | host: host ?? uri.host, 18 | port: port ?? uri.port, 19 | path: path ?? uri.path, 20 | ); 21 | 22 | Uri fromPathList({required Uri uri, required List paths}) { 23 | String path = ""; 24 | for (final element in paths) { 25 | path = chainPathSegments(path, element); 26 | } 27 | // do not double encode here because paths are already double encoded 28 | return fromUri(uri: uri, path: path); 29 | } 30 | 31 | bool compareFilePathToTargetFilePath(NcFile file, Uri destination) { 32 | return file.uri.path == 33 | chainPathSegments( 34 | destination.path, 35 | Uri.encodeComponent(file.name), 36 | ); 37 | } 38 | 39 | String chainPathSegments(String first, String second) { 40 | String firstNormalized = first; 41 | if (first.endsWith("/")) { 42 | firstNormalized = rtrim(first, "/"); 43 | } 44 | 45 | String secondNormalized = second; 46 | if (second.startsWith("/")) { 47 | secondNormalized = ltrim(second, "/"); 48 | } 49 | 50 | return "$firstNormalized/$secondNormalized"; 51 | } 52 | 53 | Uri getRootFromUri(Uri uri) => fromUri(uri: uri, path: "/"); 54 | 55 | Uri fromUriPathSegments(Uri uri, int index) { 56 | final buffer = StringBuffer(); 57 | buffer.write("/"); 58 | for (int i = 0; i <= index; i++) { 59 | // in cases where we have encoded chars in the folder name we have to re-encode 60 | // to make sure we do not change the meaning, since pathSegments does auto-decoding 61 | buffer.write("${Uri.encodeComponent(uri.pathSegments[i])}/"); 62 | } 63 | 64 | return fromUri(uri: uri, path: buffer.toString()); 65 | } 66 | 67 | String getNameFromUri(Uri uri) { 68 | if (uri.pathSegments.isEmpty) { 69 | return uri.host; 70 | } 71 | 72 | //resolving any encoded chars in the name of a file/folder to improve readability should be avoided 73 | //this would not correspond anymore to the displayed name in nextcloud 74 | //furthermore you get problems when trying to double decode DE chars 75 | if (uri.pathSegments.last.isNotEmpty) { 76 | return uri.pathSegments.last; 77 | } 78 | return uri.pathSegments[uri.pathSegments.length - 2]; 79 | } 80 | -------------------------------------------------------------------------------- /lib/views/screens/choice_selector_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/preferences/choice_preference.dart'; 3 | import 'package:yaga/views/widgets/select_cancel_bottom_navigation.dart'; 4 | 5 | class ChoiceSelectorScreen extends StatefulWidget { 6 | static const String route = "/choiceSelectorScreen"; 7 | 8 | final ChoicePreference _choicePreference; 9 | final void Function() _onCancel; 10 | final void Function(String) _onSelect; 11 | 12 | const ChoiceSelectorScreen( 13 | this._choicePreference, this._onSelect, this._onCancel); 14 | 15 | @override 16 | _ChoiceSelectorScreenState createState() => _ChoiceSelectorScreenState(); 17 | } 18 | 19 | class _ChoiceSelectorScreenState extends State { 20 | late String _choice; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _choice = widget._choicePreference.value; 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | appBar: AppBar( 32 | title: Text(widget._choicePreference.title!), 33 | ), 34 | body: ListView.separated( 35 | itemBuilder: (context, index) => RadioListTile( 36 | title: Text(widget._choicePreference.choices[ 37 | widget._choicePreference.choices.keys.elementAt(index)]!), 38 | value: widget._choicePreference.choices.keys.elementAt(index), 39 | groupValue: _choice, 40 | onChanged: (String? value) => 41 | setState(() => _choice = value ?? _choice)), 42 | separatorBuilder: (context, index) => const Divider(), 43 | itemCount: widget._choicePreference.choices.length), 44 | bottomNavigationBar: SelectCancelBottomNavigation( 45 | onCommit: () { 46 | widget._onSelect(_choice); 47 | }, 48 | onCancel: widget._onCancel), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/views/screens/favorites_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/views/screens/browse_view.dart'; 2 | 3 | class FavoritesView extends BrowseView { 4 | @override 5 | String get pref => "favorites"; 6 | 7 | const FavoritesView() : super(favorites: true); 8 | } 9 | -------------------------------------------------------------------------------- /lib/views/screens/focus_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/category_view_config.dart'; 2 | import 'package:yaga/utils/uri_utils.dart'; 3 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 4 | import 'package:yaga/views/screens/category_view_screen.dart'; 5 | 6 | class FocusView extends CategoryViewScreen { 7 | static const String route = "/focus"; 8 | 9 | FocusView(Uri path, bool favorites, YagaHomeTab selectedTab, String prefPrefix) 10 | : super( 11 | CategoryViewConfig( 12 | defaultPath: path, 13 | pref: "$prefPrefix/focus", 14 | pathEnabled: false, 15 | hasDrawer: false, 16 | selectedTab: selectedTab, 17 | title: getNameFromUri(path), 18 | favorites: favorites, 19 | ), 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /lib/views/screens/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:yaga/model/category_view_config.dart'; 2 | import 'package:yaga/services/isolateable/system_location_service.dart'; 3 | import 'package:yaga/services/intent_service.dart'; 4 | import 'package:yaga/utils/service_locator.dart'; 5 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 6 | import 'package:yaga/views/screens/category_view_screen.dart'; 7 | 8 | class HomeView extends CategoryViewScreen { 9 | static const String pref = "category"; 10 | 11 | HomeView() 12 | : super( 13 | CategoryViewConfig( 14 | defaultPath: 15 | getIt.get().internalStorage.uri, 16 | pref: pref, 17 | pathEnabled: true, 18 | hasDrawer: true, 19 | selectedTab: YagaHomeTab.grid, 20 | title: _getTitle()), 21 | ); 22 | 23 | //todo: unify this 24 | static String _getTitle() { 25 | if (getIt.get().isOpenForSelect) { 26 | return "Selecte image..."; 27 | } 28 | 29 | return "Nextcloud Yaga"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/views/screens/nc_login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | import 'package:yaga/managers/nextcloud_manager.dart'; 4 | import 'package:yaga/model/nc_login_data.dart'; 5 | import 'package:yaga/utils/nextcloud_client_factory.dart'; 6 | import 'package:yaga/utils/service_locator.dart'; 7 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 8 | 9 | class NextCloudLoginScreen extends StatelessWidget { 10 | static const String route = "/nc/login"; 11 | final Uri _url; 12 | 13 | const NextCloudLoginScreen(this._url); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | final WebViewController controller = WebViewController() 18 | ..setUserAgent(getIt.get().userAgent) 19 | ..setJavaScriptMode(JavaScriptMode.unrestricted) 20 | ..setNavigationDelegate( 21 | NavigationDelegate( 22 | onNavigationRequest: (NavigationRequest request) async { 23 | if (request.url.startsWith("nc")) { 24 | //string of type: nc://login/server:&user:&password: 25 | final Map ncParas = request.url 26 | .split("nc://login/")[1] 27 | .split("&") 28 | .map((e) => e.split(":")) 29 | .map((e) => {e.removeAt(0): e.join(":")}) 30 | .reduce((value, element) { 31 | value.addAll(element); 32 | return value; 33 | }); 34 | 35 | getIt.get().loginCommand(NextCloudLoginData( 36 | Uri.parse(ncParas[NextCloudLoginDataKeys.server]!), 37 | Uri.decodeComponent(ncParas[NextCloudLoginDataKeys.user]!), 38 | ncParas[NextCloudLoginDataKeys.password]!, 39 | )); 40 | 41 | Navigator.popUntil( 42 | context, ModalRoute.withName(YagaHomeScreen.route)); 43 | return NavigationDecision.prevent; 44 | } 45 | 46 | return NavigationDecision.navigate; 47 | }, 48 | ), 49 | ) 50 | ..loadRequest( 51 | Uri.parse("$_url/index.php/login/flow"), 52 | headers: {"OCS-APIREQUEST": "true"}, 53 | ); 54 | 55 | return Scaffold( 56 | appBar: AppBar( 57 | title: const Text("Sign in..."), 58 | ), 59 | body: WebViewWidget( 60 | controller: controller, 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/views/screens/path_selector_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/nc_file.dart'; 3 | import 'package:yaga/model/route_args/directory_navigation_screen_arguments.dart'; 4 | import 'package:yaga/views/screens/directory_traversal_screen.dart'; 5 | import 'package:yaga/views/widgets/image_views/nc_list_view.dart'; 6 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 7 | import 'package:yaga/views/widgets/select_cancel_bottom_navigation.dart'; 8 | 9 | //todo: is it a good idea to merge PathSelectorScreen and DirectoryTraversalScreen? 10 | class PathSelectorScreen extends StatelessWidget { 11 | static const String route = "/pathSelector"; 12 | 13 | final Uri _uri; 14 | final void Function(Uri)? _onSelect; 15 | final void Function(List, int)? onFileTap; 16 | final String? title; 17 | final bool fixedOrigin; 18 | final String schemeFilter; 19 | 20 | const PathSelectorScreen( 21 | this._uri, 22 | this._onSelect, { 23 | this.onFileTap, 24 | this.title, 25 | this.fixedOrigin = false, 26 | this.schemeFilter = "", 27 | }); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return DirectoryTraversalScreen(_getArgs(context)); 32 | } 33 | 34 | DirectoryNavigationScreenArguments _getArgs(BuildContext context) { 35 | Widget Function(BuildContext, Uri)? bottomBarBuilder; 36 | 37 | //todo: can't we simply build the bottomBar every time in this screen? 38 | if (_onSelect != null) { 39 | bottomBarBuilder = 40 | (BuildContext context, Uri uri) => SelectCancelBottomNavigation( 41 | onCommit: () { 42 | Navigator.of(context) 43 | .pop(DirectoryTraversalScreenNavActions.cancel); 44 | _onSelect!(uri); 45 | }, 46 | onCancel: () => Navigator.of(context) 47 | .pop(DirectoryTraversalScreenNavActions.cancel), 48 | ); 49 | } 50 | 51 | final ViewConfiguration viewConfig = ViewConfiguration.browse( 52 | route: route, 53 | defaultView: NcListView.viewKey, 54 | onFolderTap: null, 55 | onFileTap: onFileTap, 56 | onSelect: null, 57 | ); 58 | 59 | return DirectoryNavigationScreenArguments( 60 | uri: _uri, 61 | title: title ?? "Select path...", 62 | viewConfig: viewConfig, 63 | fixedOrigin: fixedOrigin, 64 | schemeFilter: schemeFilter, 65 | bottomBarBuilder: bottomBarBuilder); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/views/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:yaga/utils/nextcloud_colors.dart'; 4 | 5 | class SplashScreen extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | final scaffold = Scaffold( 9 | backgroundColor: Colors.transparent, 10 | body: Row( 11 | mainAxisAlignment: MainAxisAlignment.center, 12 | // mainAxisSize: MainAxisSize.max, 13 | children: [ 14 | Align( 15 | // alignment: Alignment.center, 16 | child: SvgPicture.asset( 17 | "assets/icon/foreground.svg", 18 | semanticsLabel: 'Yaga Logo', 19 | // alignment: Alignment.center, 20 | width: 108, 21 | ), 22 | ), 23 | ], 24 | ), 25 | ); 26 | 27 | return Container( 28 | decoration: const BoxDecoration( 29 | gradient: LinearGradient( 30 | begin: Alignment.topRight, 31 | end: Alignment.bottomLeft, 32 | colors: [ 33 | NextcloudColors.lightBlue, 34 | NextcloudColors.darkBlue, 35 | ], 36 | ), 37 | ), 38 | child: scaffold, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/views/widgets/action_danger_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ActionDangerDialog extends StatelessWidget { 4 | final String title; 5 | final String cancelButton; 6 | final String? normalAction; 7 | final String aggressiveAction; 8 | final Function(bool) action; 9 | final List Function(BuildContext) bodyBuilder; 10 | 11 | const ActionDangerDialog({ 12 | required this.title, 13 | required this.cancelButton, 14 | this.normalAction, 15 | required this.aggressiveAction, 16 | required this.action, 17 | required this.bodyBuilder, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | final actions = []; 23 | 24 | actions.add( 25 | TextButton( 26 | onPressed: () { 27 | Navigator.pop(context); 28 | }, 29 | child: Text(cancelButton), 30 | ), 31 | ); 32 | 33 | if (normalAction != null) { 34 | actions.add( 35 | TextButton( 36 | onPressed: () { 37 | Navigator.pop(context); 38 | action(false); 39 | }, 40 | child: Text(normalAction!), 41 | ), 42 | ); 43 | } 44 | 45 | actions.add( 46 | TextButton( 47 | style: TextButton.styleFrom(primary: Colors.red), 48 | onPressed: () { 49 | Navigator.pop(context); 50 | action(true); 51 | }, 52 | child: Text(aggressiveAction), 53 | ), 54 | ); 55 | 56 | return AlertDialog( 57 | title: Text(title), 58 | content: SingleChildScrollView( 59 | child: ListBody( 60 | children: bodyBuilder(context), 61 | ), 62 | ), 63 | actions: actions, 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/views/widgets/address_form_advanced.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:validators/sanitizers.dart'; 3 | 4 | class AddressFormAdvanced extends StatelessWidget { 5 | final GlobalKey _formKey; 6 | final Function(Uri) _onSave; 7 | 8 | const AddressFormAdvanced(this._formKey, this._onSave); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Form( 13 | key: _formKey, 14 | autovalidateMode: AutovalidateMode.onUserInteraction, 15 | child: TextFormField( 16 | decoration: const InputDecoration( 17 | labelText: "Fully qualified Nextcloud Server address...", 18 | icon: Icon(Icons.cloud_queue), 19 | helperStyle: TextStyle(color: Colors.orange), 20 | helperText: "Validation is disabled."), 21 | onSaved: (value) => _onSave( 22 | Uri.parse(rtrim(value?.trim()??'', "/")), 23 | ), 24 | initialValue: "https://", 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/views/widgets/address_form_simple.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:validators/sanitizers.dart'; 3 | import 'package:validators/validators.dart'; 4 | 5 | class AddressFormSimple extends StatelessWidget { 6 | final GlobalKey _formKey; 7 | final Function(Uri) _onSave; 8 | 9 | const AddressFormSimple(this._formKey, this._onSave); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Form( 14 | key: _formKey, 15 | autovalidateMode: AutovalidateMode.onUserInteraction, 16 | child: TextFormField( 17 | decoration: const InputDecoration( 18 | labelText: "Nextcloud Server address https://...", 19 | icon: Icon(Icons.cloud_queue)), 20 | onSaved: (value) => _onSave( 21 | Uri.parse('https://${rtrim(value?.trim()??"", "/")}'), 22 | ), 23 | validator: (value) { 24 | value = value?.trim()??""; 25 | if (value.startsWith("https://") || value.startsWith("http://")) { 26 | return "Https will be added automaically."; 27 | } 28 | return isURL("https://$value") ? null : "Please enter a valid URL."; 29 | }, 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/views/widgets/avatar_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:rx_command/rx_command.dart'; 5 | 6 | class AvatarWidget extends StatelessWidget { 7 | final File? _avatar; 8 | final RxCommand? _command; 9 | final IconData? _iconData; 10 | final double _radius; 11 | final bool border; 12 | 13 | const AvatarWidget(this._avatar, {double radius = 14, this.border = true}) 14 | : _command = null, 15 | _iconData = null, 16 | _radius = radius; 17 | const AvatarWidget.command(this._command, 18 | {double radius = 14, this.border = true}) 19 | : _avatar = null, 20 | _iconData = null, 21 | _radius = radius; 22 | const AvatarWidget.icon(this._iconData, 23 | {double radius = 14, this.border = true}) 24 | : _avatar = null, 25 | _command = null, 26 | _radius = radius; 27 | const AvatarWidget.phone({double radius = 14, this.border = true}) 28 | : _avatar = null, 29 | _iconData = Icons.phone_android, 30 | _command = null, 31 | _radius = radius; 32 | const AvatarWidget.sd({double radius = 14, this.border = true}) 33 | : _avatar = null, 34 | _iconData = Icons.sd_card_outlined, 35 | _command = null, 36 | _radius = radius; 37 | 38 | Widget _buildAvatar(BuildContext context, File? data) { 39 | if (border) { 40 | return CircleAvatar( 41 | radius: _radius + 1, 42 | backgroundColor: Theme.of(context).primaryColor, 43 | child: _getInnerAvatar(context, data), 44 | ); 45 | } 46 | 47 | return _getInnerAvatar(context, data); 48 | } 49 | 50 | Widget _getInnerAvatar(BuildContext context, File? data) { 51 | if (data != null && data.existsSync()) { 52 | return CircleAvatar( 53 | radius: _radius, 54 | backgroundImage: FileImage(data), 55 | ); 56 | } 57 | 58 | if (_iconData != null) { 59 | return _getIconAvatar(context, _iconData!); 60 | } 61 | 62 | return _getIconAvatar(context, Icons.cloud); 63 | } 64 | 65 | Widget _getIconAvatar(BuildContext context, IconData iconData) { 66 | return CircleAvatar( 67 | radius: _radius, 68 | backgroundColor: Theme.of(context).primaryIconTheme.color, 69 | child: Icon( 70 | iconData, 71 | size: _radius + 10, 72 | color: Colors.black, 73 | ), 74 | ); 75 | } 76 | 77 | Widget _buildAvatarFromStream(BuildContext context) { 78 | return StreamBuilder( 79 | stream: _command, 80 | initialData: _command!.lastResult, 81 | builder: (context, snapshot) => _buildAvatar(context, snapshot.data), 82 | ); 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | return _command == null 88 | ? _buildAvatar(context, _avatar) 89 | : _buildAvatarFromStream(context); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/views/widgets/circle_avatar_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CircleAvatarIcon extends StatelessWidget { 4 | final Icon icon; 5 | final double radius; 6 | 7 | const CircleAvatarIcon({ 8 | required this.icon, 9 | this.radius = 13, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Stack( 15 | alignment: Alignment.center, 16 | children: [ 17 | CircleAvatar( 18 | radius: radius, 19 | backgroundColor: Colors.white, 20 | ), 21 | icon, 22 | ], 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/widgets/favorite_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/views/widgets/circle_avatar_icon.dart'; 3 | 4 | class FavoriteIcon extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return const Align( 8 | alignment: Alignment.bottomLeft, 9 | child: CircleAvatarIcon( 10 | icon: Icon( 11 | Icons.stars, 12 | color: Colors.amber, 13 | ), 14 | ), 15 | ); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /lib/views/widgets/folder_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/nc_file.dart'; 3 | import 'package:yaga/views/widgets/favorite_icon.dart'; 4 | 5 | class FolderIcon extends StatelessWidget { 6 | final NcFile dir; 7 | final double size; 8 | 9 | const FolderIcon({super.key, required this.dir, this.size = 48}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | Stack stack = Stack(children: [ 14 | Icon( 15 | Icons.folder, 16 | size: size, 17 | ), 18 | ]); 19 | 20 | if (dir.favorite) { 21 | stack.children.add(FavoriteIcon()); 22 | } 23 | 24 | return SizedBox( 25 | height: size, 26 | width: size, 27 | child: stack, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/views/widgets/image_search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/nc_file.dart'; 3 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 4 | import 'package:yaga/views/widgets/image_view_container.dart'; 5 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 6 | 7 | class ImageSearch extends SearchDelegate { 8 | final FileListLocalManager _fileListLocalManager; 9 | final ViewConfiguration _viewConfig; 10 | 11 | ImageSearch(this._fileListLocalManager, this._viewConfig); 12 | 13 | @override 14 | ThemeData appBarTheme(BuildContext context) { 15 | //todo: keep track of this issue and improve: https://github.com/flutter/flutter/issues/45498 16 | assert(context != null); 17 | final ThemeData theme = Theme.of(context); 18 | assert(theme != null); 19 | return theme.copyWith( 20 | inputDecorationTheme: InputDecorationTheme( 21 | hintStyle: 22 | TextStyle(color: theme.primaryTextTheme.headline5?.color)), 23 | textTheme: theme.textTheme.copyWith( 24 | headline5: theme.textTheme.headline5 25 | ?.copyWith(color: theme.primaryTextTheme.headline5?.color), 26 | headline6: const TextStyle( 27 | color: Colors.white, 28 | fontWeight: FontWeight.normal, 29 | fontSize: 18, 30 | ), 31 | )); 32 | } 33 | 34 | @override 35 | List buildActions(BuildContext context) { 36 | return [ 37 | IconButton(icon: const Icon(Icons.close), onPressed: () => query = "") 38 | ]; 39 | } 40 | 41 | @override 42 | Widget buildLeading(BuildContext context) { 43 | return IconButton( 44 | icon: const Icon(Icons.arrow_back), 45 | onPressed: () => Navigator.pop(context)); 46 | } 47 | 48 | @override 49 | Widget buildResults(BuildContext context) { 50 | return ImageViewContainer( 51 | fileListLocalManager: _fileListLocalManager, 52 | viewConfig: ViewConfiguration.fromViewConfig( 53 | viewConfig: _viewConfig, 54 | onFolderTap: (NcFile file) => close(context, file), 55 | ), 56 | filter: (NcFile file) => 57 | file.name.toLowerCase().contains(query.toLowerCase()) || 58 | file.lastModified.toString().contains(query), 59 | ); 60 | } 61 | 62 | @override 63 | Widget buildSuggestions(BuildContext context) { 64 | return ListView( 65 | //children: [], 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/views/widgets/image_views/nc_grid_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/sorted_file_folder_list.dart'; 3 | import 'package:yaga/views/widgets/image_views/utils/grid_delegate.dart'; 4 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 5 | import 'package:yaga/views/widgets/remote_folder_widget.dart'; 6 | import 'package:yaga/views/widgets/remote_image_widget.dart'; 7 | 8 | class NcGridView extends StatelessWidget with GridDelegate { 9 | static const String viewKey = "grid"; 10 | final SortedFileFolderList sorted; 11 | final ViewConfiguration viewConfig; 12 | 13 | const NcGridView(this.sorted, this.viewConfig); 14 | 15 | Widget _buildImage(int key, BuildContext context) { 16 | return InkWell( 17 | onTap: () => viewConfig.onFileTap?.call(sorted.files, key), 18 | onLongPress: () => viewConfig.onSelect?.call(sorted.files, key), 19 | child: RemoteImageWidget( 20 | sorted.files[key], 21 | key: ValueKey(sorted.files[key].uri.path), 22 | cacheWidth: 256, 23 | // cacheHeight: 256, 24 | ), 25 | ); 26 | } 27 | 28 | Widget _buildFolder(int key, BuildContext context) { 29 | return Card( 30 | borderOnForeground: true, 31 | elevation: 2.0, 32 | child: Center( 33 | child: RemoteFolderWidget(index: key, sorted: sorted, viewConfig: viewConfig,) 34 | ), 35 | ); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | final SliverGrid folderGrid = SliverGrid( 41 | delegate: SliverChildBuilderDelegate( 42 | (context, index) => _buildFolder(index, context), 43 | childCount: sorted.folders.length, 44 | ), 45 | gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( 46 | maxCrossAxisExtent: 300.0, 47 | crossAxisSpacing: 2, 48 | mainAxisSpacing: 2, 49 | mainAxisExtent: 70, 50 | ), 51 | ); 52 | 53 | final SliverPadding paddedFolders = SliverPadding( 54 | padding: const EdgeInsets.all(5), 55 | sliver: folderGrid, 56 | ); 57 | 58 | final SliverGrid fileGrid = SliverGrid( 59 | gridDelegate: buildImageGridDelegate(context), 60 | delegate: SliverChildBuilderDelegate( 61 | (context, index) => _buildImage(index, context), 62 | childCount: sorted.files.length, 63 | ), 64 | ); 65 | 66 | final SliverPadding paddedFiles = SliverPadding( 67 | padding: const EdgeInsets.all(5), 68 | sliver: fileGrid, 69 | ); 70 | 71 | return CustomScrollView( 72 | physics: const AlwaysScrollableScrollPhysics(), 73 | slivers: viewConfig.showFolders.value 74 | ? [paddedFolders, paddedFiles] 75 | : [paddedFiles], 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/views/widgets/image_views/nc_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/sorted_file_folder_list.dart'; 3 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 4 | import 'package:yaga/views/widgets/remote_folder_widget.dart'; 5 | import 'package:yaga/views/widgets/remote_image_widget.dart'; 6 | 7 | class NcListView extends StatelessWidget { 8 | static const String viewKey = "list"; 9 | final SortedFileFolderList sorted; 10 | final ViewConfiguration viewConfig; 11 | 12 | const NcListView({ 13 | required this.sorted, 14 | required this.viewConfig, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final slivers = []; 20 | 21 | const Widget divider = Divider( 22 | thickness: 2, 23 | ); 24 | 25 | if (viewConfig.showFolders.value) { 26 | slivers.add( 27 | SliverList.separated( 28 | separatorBuilder: (context, index) => divider, 29 | itemBuilder: (context, index) => RemoteFolderWidget(sorted: sorted, index: index, viewConfig: viewConfig), 30 | itemCount: sorted.folders.length, 31 | ), 32 | ); 33 | } 34 | 35 | slivers.add( 36 | SliverList.list( 37 | children: const [ 38 | divider, 39 | ], 40 | ), 41 | ); 42 | 43 | slivers.add( 44 | SliverList.separated( 45 | separatorBuilder: (context, index) => divider, 46 | itemBuilder: (context, index) => ListTile( 47 | leading: SizedBox( 48 | width: 64, 49 | height: 64, 50 | child: RemoteImageWidget( 51 | sorted.files[index], 52 | key: ValueKey(sorted.files[index].uri.path), 53 | cacheWidth: 128, 54 | showFileEnding: false, 55 | ), 56 | ), 57 | title: Text(sorted.files[index].name), 58 | onTap: () => viewConfig.onFileTap?.call(sorted.files, index), 59 | onLongPress: () => viewConfig.onSelect?.call(sorted.files, index), 60 | ), 61 | itemCount: sorted.files.length, 62 | ), 63 | ); 64 | 65 | return CustomScrollView( 66 | physics: const AlwaysScrollableScrollPhysics(), 67 | slivers: slivers, 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/views/widgets/image_views/utils/grid_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | mixin class GridDelegate { 4 | SliverGridDelegate buildImageGridDelegate(BuildContext context) { 5 | return const SliverGridDelegateWithMaxCrossAxisExtent( 6 | maxCrossAxisExtent: 175.0, 7 | crossAxisSpacing: 2, 8 | mainAxisSpacing: 2, 9 | ); 10 | } 11 | } -------------------------------------------------------------------------------- /lib/views/widgets/list_menu_entry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ListMenuEntry extends StatelessWidget { 4 | final IconData _iconData; 5 | final String _text; 6 | 7 | const ListMenuEntry(this._iconData, this._text); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListTile( 12 | leading: Icon(_iconData), 13 | title: Text(_text), 14 | // isThreeLine: false, 15 | contentPadding: const EdgeInsets.all(0), 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/action_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/preferences/action_preference.dart'; 3 | 4 | class ActionPreferenceWidget extends StatelessWidget { 5 | final ActionPreference _pref; 6 | 7 | const ActionPreferenceWidget(this._pref); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListTile( 12 | title: Text( 13 | _pref.title!, 14 | ), 15 | onTap: _pref.action, 16 | enabled: _pref.enabled!, 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/bool_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rx_command/rx_command.dart'; 3 | import 'package:yaga/managers/settings_manager.dart'; 4 | import 'package:yaga/model/preferences/bool_preference.dart'; 5 | import 'package:yaga/model/preferences/preference.dart'; 6 | import 'package:yaga/services/shared_preferences_service.dart'; 7 | import 'package:yaga/utils/service_locator.dart'; 8 | import 'package:yaga/views/widgets/preferences/preference_list_tile_widget.dart'; 9 | 10 | class BoolPreferenceWidget extends StatelessWidget { 11 | final BoolPreference _defaultPreference; 12 | final RxCommand? onChangeCommand; 13 | 14 | const BoolPreferenceWidget(this._defaultPreference, {this.onChangeCommand}); 15 | 16 | //todo: generalize this for all preferences 17 | void _notifyChange(BoolPreference pref) { 18 | if (onChangeCommand != null) { 19 | onChangeCommand!(pref); 20 | return; 21 | } 22 | getIt.get().persistBoolSettingCommand(pref); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return PreferenceListTileWidget( 28 | initData: getIt 29 | .get() 30 | .loadPreferenceFromBool(_defaultPreference), 31 | listTileBuilder: (context, pref) => SwitchListTile( 32 | title: Text(pref.title!), 33 | value: pref.value, 34 | onChanged: pref.enabled! ? 35 | (value) => _notifyChange(pref.rebuild((b) => b..value = value)) : 36 | null, 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/choice_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rx_command/rx_command.dart'; 3 | import 'package:yaga/managers/settings_manager.dart'; 4 | import 'package:yaga/model/preferences/choice_preference.dart'; 5 | import 'package:yaga/model/preferences/preference.dart'; 6 | import 'package:yaga/model/route_args/choice_selector_screen_arguments.dart'; 7 | import 'package:yaga/services/shared_preferences_service.dart'; 8 | import 'package:yaga/utils/service_locator.dart'; 9 | import 'package:yaga/views/screens/choice_selector_screen.dart'; 10 | import 'package:yaga/views/widgets/preferences/preference_list_tile_widget.dart'; 11 | 12 | class ChoicePreferenceWidget extends StatelessWidget { 13 | final ChoicePreference _choicePreference; 14 | final RxCommand? onChangeCommand; 15 | 16 | const ChoicePreferenceWidget(this._choicePreference, this.onChangeCommand); 17 | 18 | //todo: generalize this for all preferences 19 | void _notifyChange(ChoicePreference pref) { 20 | if (onChangeCommand != null) { 21 | onChangeCommand!(pref); 22 | return; 23 | } 24 | getIt.get().persistStringSettingCommand(pref); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return PreferenceListTileWidget( 30 | initData: getIt 31 | .get() 32 | .loadPreferenceFromString(_choicePreference), 33 | listTileBuilder: (context, pref) => ListTile( 34 | title: Text(pref.title!), 35 | subtitle: Text(pref.choices[pref.value]!), 36 | onTap: () => Navigator.pushNamed( 37 | context, ChoiceSelectorScreen.route, 38 | arguments: 39 | ChoiceSelectorScreenArguments(pref, (String value) { 40 | Navigator.pop(context); 41 | _notifyChange(pref.rebuild((b) => b..value = value)); 42 | }, () => Navigator.pop(context))), 43 | )); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/preference_list_tile_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:yaga/managers/settings_manager.dart'; 4 | import 'package:yaga/model/preferences/preference.dart'; 5 | import 'package:yaga/utils/service_locator.dart'; 6 | 7 | class PreferenceListTileWidget extends StatelessWidget { 8 | final T initData; 9 | final Widget Function(BuildContext, T) listTileBuilder; 10 | 11 | const PreferenceListTileWidget({ 12 | required this.initData, 13 | required this.listTileBuilder, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return StreamBuilder( 19 | stream: getIt 20 | .get() 21 | .updateSettingCommand 22 | .where((event) => event.key == initData.key) 23 | .map((event) => event as T), 24 | initialData: initData, 25 | builder: (BuildContext context, AsyncSnapshot snapshot) { 26 | return listTileBuilder(context, snapshot.data!); 27 | }, 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/section_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/preferences/preference.dart'; 3 | 4 | class SectionPreferenceWidget extends StatelessWidget { 5 | final Preference _pref; 6 | 7 | const SectionPreferenceWidget(this._pref); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListTile( 12 | title: Text( 13 | _pref.title!, 14 | style: TextStyle( 15 | color: Theme.of(context).colorScheme.secondary, fontWeight: FontWeight.bold), 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/string_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/model/preferences/string_preference.dart'; 3 | import 'package:yaga/services/shared_preferences_service.dart'; 4 | import 'package:yaga/utils/service_locator.dart'; 5 | import 'package:yaga/views/widgets/preferences/preference_list_tile_widget.dart'; 6 | 7 | class StringPreferenceWidget extends StatelessWidget { 8 | final StringPreference _defaultPreference; 9 | final Function(StringPreference) _onTap; 10 | 11 | const StringPreferenceWidget(this._defaultPreference, this._onTap); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return PreferenceListTileWidget( 16 | initData: getIt 17 | .get() 18 | .loadPreferenceFromString(_defaultPreference), 19 | listTileBuilder: (context, pref) => ListTile( 20 | title: Text(pref.title!), 21 | subtitle: Text(pref.value), 22 | onTap: () => _onTap(pref), 23 | )); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/widgets/preferences/uri_preference_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:rx_command/rx_command.dart'; 3 | import 'package:yaga/managers/settings_manager.dart'; 4 | import 'package:yaga/model/preferences/preference.dart'; 5 | import 'package:yaga/model/preferences/uri_preference.dart'; 6 | import 'package:yaga/model/route_args/path_selector_screen_arguments.dart'; 7 | import 'package:yaga/services/name_exchange_service.dart'; 8 | import 'package:yaga/services/shared_preferences_service.dart'; 9 | import 'package:yaga/utils/service_locator.dart'; 10 | import 'package:yaga/views/screens/path_selector_screen.dart'; 11 | import 'package:yaga/views/widgets/preferences/preference_list_tile_widget.dart'; 12 | 13 | class UriPreferenceWidget extends StatelessWidget { 14 | final UriPreference _defaultPref; 15 | final RxCommand? onChangeCommand; 16 | 17 | const UriPreferenceWidget(this._defaultPref, {this.onChangeCommand}); 18 | 19 | void _notifyChange(UriPreference pref) { 20 | if (onChangeCommand != null) { 21 | onChangeCommand!(pref); 22 | return; 23 | } 24 | getIt.get().persistStringSettingCommand(pref); 25 | } 26 | 27 | void _pushToNavigation(BuildContext context, UriPreference pref, Uri uri) { 28 | Navigator.pushNamed( 29 | context, 30 | PathSelectorScreen.route, 31 | arguments: PathSelectorScreenArguments( 32 | uri: uri, 33 | onSelect: (Uri path) => _notifyChange( 34 | pref.rebuild((b) => b..value = path), 35 | ), 36 | fixedOrigin: pref.fixedOrigin, 37 | schemeFilter: pref.schemeFilter, 38 | ), 39 | ); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return PreferenceListTileWidget( 45 | initData: getIt 46 | .get() 47 | .loadPreferenceFromString(_defaultPref), 48 | listTileBuilder: (context, pref) => ListTile( 49 | enabled: pref.enabled!, 50 | title: Text(pref.title!), 51 | subtitle: Text(Uri.decodeComponent(getIt.get().convertUriToHumanReadableUri(pref.value).toString())), 52 | onTap: () => _pushToNavigation(context, pref, pref.value), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/views/widgets/remote_folder_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/file_manager/file_manager.dart'; 3 | import 'package:yaga/model/nc_file.dart'; 4 | import 'package:yaga/model/sorted_file_folder_list.dart'; 5 | import 'package:yaga/utils/service_locator.dart'; 6 | import 'package:yaga/views/widgets/folder_icon.dart'; 7 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 8 | 9 | class RemoteFolderWidget extends StatelessWidget { 10 | 11 | final SortedFileFolderList sorted; 12 | final int index; 13 | final ViewConfiguration viewConfig; 14 | 15 | const RemoteFolderWidget({super.key, required this.sorted, required this.index, required this.viewConfig}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | final folder = sorted.folders[index]; 20 | return StreamBuilder(stream: getIt.get().updateImageCommand 21 | .where((event) => event.uri.path == folder.uri.path), 22 | initialData: folder, 23 | builder: (context, snapshot) => ListTile( 24 | onLongPress: () => viewConfig.onSelect?.call(sorted.folders, index), 25 | onTap: () => viewConfig.onFolderTap?.call(snapshot.data!), 26 | leading: FolderIcon(dir: snapshot.data!), 27 | trailing: snapshot.data!.selected ? const Icon(Icons.check_circle) : null, 28 | title: Text( 29 | folder.name, 30 | ), 31 | ), 32 | ); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/views/widgets/search_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 3 | import 'package:yaga/model/nc_file.dart'; 4 | import 'package:yaga/views/widgets/image_search.dart'; 5 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 6 | 7 | class SearchIconButton extends StatelessWidget { 8 | final FileListLocalManager fileListLocalManager; 9 | final ViewConfiguration viewConfig; 10 | final Function(NcFile?)? searchResultHandler; 11 | 12 | const SearchIconButton({ 13 | required this.fileListLocalManager, 14 | required this.viewConfig, 15 | this.searchResultHandler, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return IconButton( 21 | icon: const Icon(Icons.search), 22 | onPressed: () async { 23 | final NcFile? file = await showSearch( 24 | context: context, 25 | delegate: ImageSearch(fileListLocalManager, viewConfig), 26 | ); 27 | searchResultHandler?.call(file); 28 | }, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/views/widgets/select_cancel_bottom_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SelectCancelBottomNavigation extends StatelessWidget { 4 | final Function onCommit; 5 | final Function onCancel; 6 | final String labelSelect; 7 | final String labelCancel; 8 | final IconData iconSelect; 9 | final List betweenItems; 10 | final List betweenItemsCallbacks; 11 | 12 | const SelectCancelBottomNavigation({ 13 | required this.onCommit, 14 | required this.onCancel, 15 | this.labelSelect = "Select", 16 | this.labelCancel = "Cancel", 17 | this.iconSelect = Icons.check, 18 | this.betweenItems = const [], 19 | this.betweenItemsCallbacks = const [], 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | final List items = []; 25 | items.add(BottomNavigationBarItem( 26 | icon: const Icon(Icons.close), 27 | label: labelCancel, 28 | )); 29 | items.addAll(betweenItems); 30 | items.add(BottomNavigationBarItem( 31 | icon: Icon(iconSelect), 32 | label: labelSelect, 33 | )); 34 | 35 | return BottomNavigationBar( 36 | currentIndex: items.length - 1, 37 | onTap: (index) { 38 | if (index == items.length - 1) { 39 | onCommit(); 40 | return; 41 | } 42 | 43 | if (index == 0) { 44 | onCancel(); 45 | return; 46 | } 47 | 48 | final betweenItemsIndex = index - 1; 49 | if (betweenItemsIndex >= 0) { 50 | betweenItemsCallbacks[betweenItemsIndex].call(); 51 | return; 52 | } 53 | }, 54 | items: items, 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/views/widgets/selection_action_cancel_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SelectionActionCancelDialog extends StatelessWidget { 4 | final String title; 5 | final Function() cancelAction; 6 | 7 | const SelectionActionCancelDialog(this.title, this.cancelAction); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return AlertDialog( 12 | title: Text(title), 13 | content: const SingleChildScrollView( 14 | child: Center( 15 | child: CircularProgressIndicator(), 16 | ), 17 | ), 18 | actions: [ 19 | TextButton( 20 | onPressed: () => cancelAction(), 21 | child: const Text('Cancel'), 22 | ), 23 | ], 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/views/widgets/selection_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 3 | import 'package:yaga/model/nc_file.dart'; 4 | import 'package:yaga/views/widgets/image_views/utils/view_configuration.dart'; 5 | import 'package:yaga/views/widgets/search_icon_button.dart'; 6 | import 'package:yaga/views/widgets/selection_popup_menu_button.dart'; 7 | import 'package:yaga/views/widgets/selection_select_all.dart'; 8 | 9 | class SelectionAppBar extends PreferredSize { 10 | factory SelectionAppBar({ 11 | required FileListLocalManager fileListLocalManager, 12 | required ViewConfiguration viewConfig, 13 | required Widget Function(BuildContext, List) appBarBuilder, 14 | double bottomHeight = 0, 15 | Function(NcFile?)? searchResultHandler, 16 | }) { 17 | final Widget child = StreamBuilder( 18 | initialData: fileListLocalManager.selectionModeChanged.lastResult, 19 | stream: fileListLocalManager.selectionModeChanged, 20 | builder: (context, snapshot) { 21 | final List actions = []; 22 | if (fileListLocalManager.isInSelectionMode) { 23 | actions.add(SelectionSelectAll(fileListLocalManager)); 24 | } 25 | 26 | actions.add(SearchIconButton( 27 | fileListLocalManager: fileListLocalManager, 28 | viewConfig: viewConfig, 29 | searchResultHandler: searchResultHandler, 30 | )); 31 | 32 | if (fileListLocalManager.isInSelectionMode) { 33 | actions.add(SelectionPopupMenuButton( 34 | fileListLocalManager: fileListLocalManager, 35 | )); 36 | } 37 | 38 | return appBarBuilder(context, actions); 39 | }, 40 | ); 41 | 42 | return SelectionAppBar._internal( 43 | preferredSize: Size.fromHeight(kToolbarHeight + bottomHeight), 44 | child: child, 45 | ); 46 | } 47 | 48 | const SelectionAppBar._internal({ 49 | required Size preferredSize, 50 | required Widget child, 51 | }) : super(preferredSize: preferredSize, child: child); 52 | } 53 | -------------------------------------------------------------------------------- /lib/views/widgets/selection_select_all.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 3 | 4 | class SelectionSelectAll extends StatelessWidget { 5 | final FileListLocalManager _fileListLocalManager; 6 | 7 | const SelectionSelectAll(this._fileListLocalManager); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return IconButton( 12 | icon: const Icon(Icons.select_all), 13 | onPressed: _fileListLocalManager.toggleSelect, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/views/widgets/selection_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 3 | 4 | class SelectionTitle extends StatelessWidget { 5 | final FileListLocalManager _fileListLocalManager; 6 | final Widget? defaultTitel; 7 | 8 | const SelectionTitle(this._fileListLocalManager, {this.defaultTitel}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return StreamBuilder( 13 | stream: _fileListLocalManager.selectionChangedCommand, 14 | builder: (context, snapshot) { 15 | if (defaultTitel != null && !_fileListLocalManager.isInSelectionMode) { 16 | return defaultTitel!; 17 | } 18 | return Text( 19 | "${_fileListLocalManager.selected.length}/${_fileListLocalManager.sorted.length}", 20 | overflow: TextOverflow.fade, 21 | ); 22 | }, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/views/widgets/selection_will_pop_scope.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/widget_local/file_list_local_manager.dart'; 3 | 4 | class SelectionWillPopScope extends StatelessWidget { 5 | final Widget child; 6 | final FileListLocalManager fileListLocalManager; 7 | 8 | const SelectionWillPopScope({ 9 | required this.child, 10 | required this.fileListLocalManager, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return WillPopScope( 16 | onWillPop: () async { 17 | if (fileListLocalManager.selectionModeChanged.lastResult!) { 18 | fileListLocalManager.deselectAll(); 19 | return false; 20 | } 21 | 22 | return true; 23 | }, 24 | child: child, 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/views/widgets/yaga_about_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_markdown/flutter_markdown.dart'; 3 | import 'package:flutter_svg/svg.dart'; 4 | import 'package:markdown/markdown.dart' as mk; 5 | import 'package:package_info_plus/package_info_plus.dart'; 6 | import 'package:url_launcher/url_launcher_string.dart'; 7 | import 'package:yaga/utils/service_locator.dart'; 8 | 9 | class YagaAboutDialog extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return AboutDialog( 13 | applicationVersion: "v${getIt.get().version}", 14 | applicationIcon: SvgPicture.asset( 15 | "assets/icon/icon.svg", 16 | semanticsLabel: 'Yaga Logo', 17 | width: 56, 18 | ), 19 | children: [ 20 | FutureBuilder( 21 | future: DefaultAssetBundle.of(context).loadString("assets/news.md"), 22 | builder: (context, snapshot) { 23 | final sc = ScrollController(); 24 | return SizedBox( 25 | height: 300, 26 | width: 400, 27 | child: Scrollbar( 28 | thumbVisibility: true, 29 | controller: sc, 30 | child: Markdown( 31 | padding: const EdgeInsets.fromLTRB(0.0, 0.0, 5.0, 0.0), 32 | data: snapshot.data?.toString() ?? "", 33 | shrinkWrap: true, 34 | controller: sc, 35 | extensionSet: mk.ExtensionSet( 36 | mk.ExtensionSet.gitHubFlavored.blockSyntaxes, 37 | [ 38 | mk.EmojiSyntax(), 39 | ...mk.ExtensionSet.gitHubFlavored.inlineSyntaxes 40 | ], 41 | ), 42 | ), 43 | ), 44 | ); 45 | }, 46 | ), 47 | Align( 48 | alignment: Alignment.topLeft, 49 | child: TextButton.icon( 50 | onPressed: () => 51 | launchUrlString("https://vauvenal5.github.io/yaga-docs/"), 52 | icon: const Icon(Icons.book_outlined), 53 | label: const Text("Read the docs"), 54 | ), 55 | ), 56 | Align( 57 | alignment: Alignment.topLeft, 58 | child: TextButton.icon( 59 | onPressed: () => launchUrlString( 60 | "https://vauvenal5.github.io/yaga-docs/privacy/", 61 | ), 62 | icon: const Icon(Icons.policy_outlined), 63 | label: const Text("Privacy Policy"), 64 | ), 65 | ), 66 | Align( 67 | alignment: Alignment.topLeft, 68 | child: TextButton.icon( 69 | onPressed: () => 70 | launchUrlString("https://github.com/vauvenal5/yaga/issues"), 71 | icon: const Icon(Icons.bug_report_outlined), 72 | label: const Text("Report a bug"), 73 | ), 74 | ), 75 | ], 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/views/widgets/yaga_bottom_nav_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:yaga/managers/tab_manager.dart'; 3 | import 'package:yaga/utils/service_locator.dart'; 4 | import 'package:yaga/views/screens/yaga_home_screen.dart'; 5 | 6 | class YagaBottomNavBar extends StatelessWidget { 7 | final YagaHomeTab _selectedTab; 8 | 9 | const YagaBottomNavBar(this._selectedTab); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return BottomNavigationBar( 14 | currentIndex: _getCurrentIndex(), 15 | onTap: (index) { 16 | switch (index) { 17 | case 2: 18 | getIt.get().tabChangedCommand(YagaHomeTab.folder); 19 | return; 20 | case 1: 21 | getIt.get().tabChangedCommand(YagaHomeTab.favorites); 22 | return; 23 | default: 24 | getIt.get().tabChangedCommand(YagaHomeTab.grid); 25 | } 26 | }, 27 | items: const [ 28 | BottomNavigationBarItem( 29 | icon: Icon(Icons.home), 30 | label: 'Home', 31 | ), 32 | BottomNavigationBarItem( 33 | icon: Icon(Icons.star), 34 | label: 'Favorites', 35 | ), 36 | BottomNavigationBarItem( 37 | icon: Icon(Icons.folder), 38 | label: 'Browse', 39 | ), 40 | ], 41 | ); 42 | } 43 | 44 | int _getCurrentIndex() { 45 | switch (_selectedTab) { 46 | case YagaHomeTab.folder: 47 | return 2; 48 | case YagaHomeTab.favorites: 49 | return 1; 50 | default: 51 | return 0; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/views/widgets/yaga_popup_menu_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class YagaPopupMenuButton extends StatelessWidget { 4 | final List> Function(BuildContext context) itemBuilder; 5 | final void Function(BuildContext context, T result) handler; 6 | 7 | const YagaPopupMenuButton(this.itemBuilder, this.handler); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return PopupMenuButton( 12 | offset: const Offset(0, 10), 13 | onSelected: (T result) => handler(context, result), 14 | itemBuilder: itemBuilder, 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void fl_register_plugins(FlPluginRegistry* registry) { 13 | g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = 14 | fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); 15 | flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); 16 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 17 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 18 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 19 | } 20 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | flutter_secure_storage_linux 7 | url_launcher_linux 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:yaga/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------