├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── pureinfoapps │ │ │ │ └── android │ │ │ │ └── apps │ │ │ │ └── filestools │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── ic_launcher_monochrome.xml │ │ │ └── launch_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── app_icon_compressed.png ├── github_3d_icon.png ├── no_ads.png ├── open_source.png └── rive │ ├── animated_login_screen.riv │ ├── finger_tapping.riv │ ├── flame_and_spark.riv │ ├── flame_loader.riv │ ├── impatient_placeholder.riv │ └── rive_emoji_pack.riv ├── flutter_native_splash.yaml ├── fonts ├── LexendDeca-Black.ttf ├── LexendDeca-Bold.ttf ├── LexendDeca-ExtraBold.ttf ├── LexendDeca-ExtraLight.ttf ├── LexendDeca-Light.ttf ├── LexendDeca-Medium.ttf ├── LexendDeca-Regular.ttf ├── LexendDeca-SemiBold.ttf └── LexendDeca-Thin.ttf ├── l10n.yaml ├── lib ├── constants.dart ├── l10n │ ├── arb │ │ └── intl_en.arb │ ├── generated │ │ ├── app_locale.dart │ │ └── app_locale_en.dart │ └── untranslated.json ├── main.dart ├── models │ ├── file_model.dart │ ├── file_pick_save_model.dart │ ├── image_model.dart │ ├── pdf_page_model.dart │ └── tool_actions_model.dart ├── route │ └── app_routes.dart ├── state │ ├── app_theme_state.dart │ ├── package_info_state.dart │ ├── preferences.dart │ ├── providers.dart │ ├── tools_actions_state.dart │ └── tools_screens_state.dart ├── ui │ ├── components │ │ ├── color_picker.dart │ │ ├── crashlytics_analytics_switch.dart │ │ ├── custom_keep_alive.dart │ │ ├── custom_snack_bar.dart │ │ ├── drawer.dart │ │ ├── dynamic_theme_switch_tile.dart │ │ ├── input_output_list_tile.dart │ │ ├── levitating_options_bar.dart │ │ ├── link_button.dart │ │ ├── loading.dart │ │ ├── more_info_button.dart │ │ ├── pdf_pages_gridview_components.dart │ │ ├── reset_app_theme_settings.dart │ │ ├── select_file_section.dart │ │ ├── theme_chooser_widget.dart │ │ ├── theme_mode_switcher.dart │ │ ├── tool_actions_section.dart │ │ ├── tools_about_card.dart │ │ └── view_error.dart │ ├── screens │ │ ├── about_page.dart │ │ ├── homescreen │ │ │ ├── homescreen.dart │ │ │ └── pages │ │ │ │ ├── components │ │ │ │ ├── grid_view_in_card_view.dart │ │ │ │ ├── image_tools_section.dart │ │ │ │ └── pdf_tools_section.dart │ │ │ │ ├── document_tools_page.dart │ │ │ │ └── media_tools_page.dart │ │ ├── image_tools_screens │ │ │ ├── compress_image │ │ │ │ ├── actions │ │ │ │ │ └── compress_image.dart │ │ │ │ ├── compress_image_screen.dart │ │ │ │ └── compress_image_tool_action_screen.dart │ │ │ ├── crop_rotate_flip_images │ │ │ │ ├── actions │ │ │ │ │ └── crop_rotate_flip_images.dart │ │ │ │ ├── crop_rotate_flip_images_screen.dart │ │ │ │ └── crop_rotate_flip_images_tool_action_screen.dart │ │ │ └── pdf_to_image │ │ │ │ └── pdf_to_image_screen.dart │ │ ├── image_viewer.dart │ │ ├── onboarding_screen.dart │ │ ├── pdf_tools_screens │ │ │ ├── compress_pdf │ │ │ │ ├── actions │ │ │ │ │ └── compress_pdf.dart │ │ │ │ ├── compress_pdf_screen.dart │ │ │ │ └── compress_pdf_tool_action_screen.dart │ │ │ ├── convert_pdf │ │ │ │ ├── actions │ │ │ │ │ └── convert_to_image.dart │ │ │ │ ├── convert_pdf_screen.dart │ │ │ │ └── convert_pdf_tool_action_screen.dart │ │ │ ├── decrypt_pdf │ │ │ │ ├── actions │ │ │ │ │ └── decrypt_pdf.dart │ │ │ │ ├── decrypt_pdf_screen.dart │ │ │ │ └── decrypt_pdf_tools_action_screen.dart │ │ │ ├── encrypt_pdf │ │ │ │ ├── actions │ │ │ │ │ └── encrypt_pdf.dart │ │ │ │ ├── encrypt_pdf_screen.dart │ │ │ │ └── encrypt_pdf_tools_action_screen.dart │ │ │ ├── image_to_pdf │ │ │ │ ├── actions │ │ │ │ │ └── image_to_pdf.dart │ │ │ │ ├── image_to_pdf_screen.dart │ │ │ │ └── image_to_pdf_tools_action_screen.dart │ │ │ ├── merge_pdfs_screen.dart │ │ │ ├── modify_pdf │ │ │ │ ├── actions │ │ │ │ │ └── rotate_delete_reorder_pages.dart │ │ │ │ ├── modify_pdf_screen.dart │ │ │ │ └── modify_pdf_tool_action_screen.dart │ │ │ ├── split_pdf │ │ │ │ ├── actions │ │ │ │ │ ├── extract_by_page_range.dart │ │ │ │ │ ├── extract_pages.dart │ │ │ │ │ ├── split_by_page_count.dart │ │ │ │ │ ├── split_by_page_numbers.dart │ │ │ │ │ └── split_by_size.dart │ │ │ │ ├── split_pdf_screen.dart │ │ │ │ └── split_pdf_tools_action_screen.dart │ │ │ └── watermark_pdf │ │ │ │ ├── actions │ │ │ │ └── watermark_pdf.dart │ │ │ │ ├── watermark_pdf_screen.dart │ │ │ │ └── watermark_pdf_tool_action_screen.dart │ │ ├── pdf_viewer.dart │ │ ├── result_screen.dart │ │ └── settings_page.dart │ └── theme │ │ ├── app_theme_data.dart │ │ ├── color_schemes.g.dart │ │ └── custom_color.g.dart └── utils │ ├── decimal_text_input_formatter.dart │ ├── edit_image.dart │ ├── image_tools_actions.dart │ ├── pdf_tools_actions.dart │ ├── pick_save.dart │ └── utility.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | 46 | # Exclude these 47 | /lib/firebase_options.dart 48 | /android/app/google-services.json 49 | /ios/Runner/GoogleService-Info.plist 50 | /ios/firebase_app_id_file.json 51 | /local.properties -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 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: e3c29ec00c9c825c891d75054c63fcc46454dca1 17 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 18 | - platform: android 19 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 20 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project repository for Files Tools app 2 | 3 | [![wakatime](https://wakatime.com/badge/user/83f3b15d-49de-4c01-b8de-bbc132f11be1/project/3876495a-70a6-486e-b999-b6854d83bdc9.svg)](https://wakatime.com/badge/user/83f3b15d-49de-4c01-b8de-bbc132f11be1/project/3876495a-70a6-486e-b999-b6854d83bdc9) 4 | 5 | Get it on Google Play Get it on Galaxy Store Get it on Amazon Store 6 | 7 | **Note: "Master" branch will be developed and maintained moving forward. It was rewritten from scratch due to the shortcomings in "V1".** 8 | 9 | # Table of Contents 10 | > - [1. Introduction](#introduction) 11 | > - [2. Files Tools functionalities](#files-tools-provides-functionality-for-the-following-tasks-) 12 | > * [📄 PDF Tools](#-for-pdf-) 13 | > * [🖼️ Image Tools](#%EF%B8%8F-for-image-) 14 | > * [🔜 Tools Coming Soon](#-coming-soon-) 15 | > - [3. Screenshots](#screenshots-) 16 | > - [4. 🤔Why I spent time recreating this app?](#why-i-spent-time-recreating-this-app) 17 | 18 | ## Introduction 19 | 20 | Files Tools provides tools to perform various operations on files (documents and media), which helps everyone in their everyday life. And it aims to do that with the following goals in mind - No Ads🧘- 100% Free🆓- 100% Open Source💚. 21 | 22 | ## Files Tools provides functionality for the following tasks:- 23 | 24 | ### 📄 For PDF:- 25 | - Merge multiple PDFs 26 | - Split PDF - Extract PDF pages by selecting, Split PDF by page count, Split PDF by Size, Split PDF by page numbers, Extract PDF by page ranges. 27 | - Modify PDF - Rotate, delete, and reorder PDF pages 28 | - Convert PDF to images 29 | - Compress PDF 30 | - Watermark PDF 31 | - Convert Images To PDF 32 | - Encrypt PDF 33 | - Decrypt PDF 34 | 35 | ### 🖼️ For Image:- 36 | - Compress images 37 | - Crop, rotate and flip images 38 | - Convert PDF to images 39 | 40 | ### 🔜 Coming Soon:- 41 | - Watermark images. 42 | - Change image dimensions. 43 | 44 | Please give the project a star if you liked the app or idea behind it. 45 | 46 | ## Screenshots:- 47 | 48 | | | | 49 | | -------------- | -------------- | 50 | | | | 51 | | | | 52 | | | | 53 | 54 | ## 🤔Why I spent time recreating this app? 55 | 56 | I've been making apps for a while now, and over that time, I've learned a lot. After learning about application development and flutter, this was my first project. I was very pleased with what I produced, although it was riddled with mistakes and flaws because I didn't know much at the time. However, I liked the concept behind it, so later, when I had some free time, I recreated it while taking all of the flaws from the old version into account, and it came out far better than the old one. And I have to admit that I learnt a lot more new knowledge during this process. 57 | 58 | Please be aware of the following things as well:- 59 | - This project utilises itext7 for various operations involving PDFs. 60 | - Since itext7 AGPL V3 License is used in this project, Files Tools are also licenced under this licence. 61 | - The project/app developer, the owner of the copyright, and the contributors are not accountable or liable for any damage resulting from this project/app. 62 | 63 | ### Contribute 64 | 65 | If you wish to contribute to this project, I will be pleased. You can [email](mailto:0qs8e9yn@duck.com?subject=[GitHub]) me if you have any questions as well. 66 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | analyzer: 12 | exclude: 13 | - "**/*.g.dart" 14 | - "**/*.freezed.dart" 15 | - "test/.test_coverage.dart" 16 | - "bin/cache/**" 17 | - "lib/generated_plugin_registrant.dart" 18 | - "lib/firebase_options.dart" 19 | - "lib/l10n/**" 20 | - "test/widget_test.dart" 21 | plugins: 22 | - string_literal_finder 23 | 24 | # For more information see: 25 | # https://dart.dev/guides/language/analysis-options#enabling-additional-type-checks 26 | language: 27 | strict-casts: true 28 | strict-inference: true 29 | strict-raw-types: true 30 | 31 | errors: 32 | missing_required_param: error 33 | missing_return: error 34 | deprecated_member_use_from_same_package: ignore 35 | parameter_assignments: warning 36 | todo: ignore 37 | 38 | linter: 39 | # The lint rules applied to this project can be customized in the 40 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 41 | # included above or to enable additional rules. A list of all available lints 42 | # and their documentation is published at 43 | # https://dart-lang.github.io/linter/lints/index.html. 44 | # 45 | # Instead of disabling a lint rule for the entire project in the 46 | # section below, it can also be suppressed for a single line of code 47 | # or a specific dart file by using the `// ignore: name_of_lint` and 48 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 49 | # producing the lint. 50 | rules: 51 | always_put_control_body_on_new_line: true 52 | always_put_required_named_parameters_first: false 53 | always_specify_types: true 54 | always_use_package_imports: true 55 | avoid_relative_lib_imports: true 56 | avoid_annotating_with_dynamic: false 57 | avoid_catches_without_on_clauses: false 58 | avoid_classes_with_only_static_members: false 59 | avoid_final_parameters: false 60 | avoid_positional_boolean_parameters: false 61 | avoid_print: true 62 | avoid_redundant_argument_values: true 63 | avoid_types_on_closure_parameters: false 64 | cascade_invocations: false 65 | close_sinks: false 66 | flutter_style_todos: true 67 | lines_longer_than_80_chars: true 68 | no_default_cases: true 69 | omit_local_variable_types: false 70 | prefer_asserts_with_message: false 71 | prefer_constructors_over_static_methods: false 72 | prefer_double_quotes: false 73 | prefer_expression_function_bodies: false 74 | prefer_final_parameters: false # Enable for cleanup and then disable it as it causes false positives. 75 | prefer_int_literals: false 76 | public_member_api_docs: true 77 | require_trailing_commas: true 78 | sort_constructors_first: true 79 | unnecessary_final: false 80 | sort_pub_dependencies: false 81 | 82 | # Additional information about this file can be found at 83 | # https://dart.dev/guides/language/analysis-options 84 | 85 | # Credits on reasoning behind many lints goes to https://gist.github.com/rydmike/fdb53ddd933c37d20e6f3188a936cd4c -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | def keystoreProperties = new Properties() 25 | def keystorePropertiesFile = rootProject.file('key.properties') 26 | if (keystorePropertiesFile.exists()) { 27 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 28 | } 29 | 30 | apply plugin: 'com.android.application' 31 | apply plugin: 'kotlin-android' 32 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 33 | 34 | // Add the Google services Gradle plugin 35 | apply plugin: 'com.google.gms.google-services' 36 | apply plugin: 'com.google.firebase.crashlytics' 37 | 38 | android { 39 | compileSdkVersion 33 40 | ndkVersion "25.1.8937393" 41 | 42 | compileOptions { 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | 47 | kotlinOptions { 48 | jvmTarget = '1.8' 49 | } 50 | 51 | sourceSets { 52 | main.java.srcDirs += 'src/main/kotlin' 53 | } 54 | 55 | defaultConfig { 56 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 57 | applicationId "com.pureinfoapps.android.apps.filestools" 58 | // You can update the following values to match your application needs. 59 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 60 | minSdkVersion 21 61 | targetSdkVersion 33 62 | versionCode flutterVersionCode.toInteger() 63 | versionName flutterVersionName 64 | ndk { 65 | debugSymbolLevel 'FULL' 66 | } 67 | } 68 | 69 | signingConfigs { 70 | release { 71 | keyAlias keystoreProperties['keyAlias'] 72 | keyPassword keystoreProperties['keyPassword'] 73 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 74 | storePassword keystoreProperties['storePassword'] 75 | } 76 | } 77 | buildTypes { 78 | release { 79 | // Enables code shrinking, obfuscation, and optimization for only 80 | // your project's release build type. 81 | minifyEnabled true 82 | 83 | // Enables resource shrinking, which is performed by the 84 | // Android Gradle plugin. 85 | shrinkResources true 86 | 87 | // Includes the default ProGuard rules files that are packaged with 88 | // the Android Gradle plugin. To learn more, go to the section about 89 | // R8 configuration files. 90 | proguardFiles getDefaultProguardFile( 91 | 'proguard-android-optimize.txt'), 92 | 'proguard-rules.pro' 93 | 94 | signingConfig signingConfigs.release 95 | 96 | ndk { 97 | debugSymbolLevel 'FULL' 98 | 99 | // By default, the app bundle contains your Dart code and the Flutter 100 | // runtime compiled for armeabi-v7a (ARM 32-bit), arm64-v8a (ARM 64-bit), and x86-64 (x86 64-bit). 101 | // For reference see https://docs.flutter.dev/deployment/android#build-an-app-bundle. 102 | // But without these filters in case of some 3rd party plugins supporting x86 it will generates x86 folder 103 | // marking the app compatible with x86 even when its not, this will lead to crash on x86 devices. 104 | // For more info see https://github.com/flutter/flutter/issues/73943#issuecomment-979741146. 105 | abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' 106 | } 107 | } 108 | } 109 | 110 | } 111 | 112 | flutter { 113 | source '../..' 114 | } 115 | 116 | dependencies { 117 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 118 | } 119 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # To use iText in release mode Otherwise we get 24 | # PlatformException AbstractITextEvent is only for internal usage. 25 | -keep public class com.itextpdf.** 26 | -keep public class org.apache.** -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 19 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/pureinfoapps/android/apps/filestools/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.pureinfoapps.android.apps.filestools 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // Starting with Android 13 (API level 33), you can opt-in to providing a 7 | // monochrome drawable. 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | // Starting with Android 13 (API level 33), you can opt-in to providing a 7 | // monochrome drawable. 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #A93054 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.20' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | maven { 7 | url "https://repo.itextsupport.com/android" 8 | } 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:7.1.3' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | 15 | // Add the dependency for the Google services Gradle plugin 16 | classpath 'com.google.gms:google-services:4.3.14' 17 | 18 | classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | google() 25 | mavenCentral() 26 | maven { 27 | url "https://repo.itextsupport.com/android" 28 | } 29 | } 30 | } 31 | 32 | rootProject.buildDir = '../build' 33 | subprojects { 34 | project.buildDir = "${rootProject.buildDir}/${project.name}" 35 | } 36 | subprojects { 37 | project.evaluationDependsOn(':app') 38 | } 39 | 40 | task clean(type: Delete) { 41 | delete rootProject.buildDir 42 | } 43 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | 5 | # Added because of https://issuetracker.google.com/issues/147096055 6 | android.bundle.enableUncompressedNativeLibs=false -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /assets/app_icon_compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/app_icon_compressed.png -------------------------------------------------------------------------------- /assets/github_3d_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/github_3d_icon.png -------------------------------------------------------------------------------- /assets/no_ads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/no_ads.png -------------------------------------------------------------------------------- /assets/open_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/open_source.png -------------------------------------------------------------------------------- /assets/rive/animated_login_screen.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/animated_login_screen.riv -------------------------------------------------------------------------------- /assets/rive/finger_tapping.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/finger_tapping.riv -------------------------------------------------------------------------------- /assets/rive/flame_and_spark.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/flame_and_spark.riv -------------------------------------------------------------------------------- /assets/rive/flame_loader.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/flame_loader.riv -------------------------------------------------------------------------------- /assets/rive/impatient_placeholder.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/impatient_placeholder.riv -------------------------------------------------------------------------------- /assets/rive/rive_emoji_pack.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/assets/rive/rive_emoji_pack.riv -------------------------------------------------------------------------------- /fonts/LexendDeca-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Black.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Bold.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-ExtraBold.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-ExtraLight.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Light.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Medium.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Regular.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-SemiBold.ttf -------------------------------------------------------------------------------- /fonts/LexendDeca-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaudharydeepanshu/files_tools/c8cb46642d76d27c14aedff0873f211d349eb82d/fonts/LexendDeca-Thin.ttf -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n/arb 2 | output-dir: lib/l10n/generated 3 | template-arb-file: intl_en.arb 4 | output-localization-file: app_locale.dart 5 | output-class: AppLocale 6 | preferred-supported-locales: ["en"] 7 | synthetic-package: false 8 | nullable-getter: false 9 | format: false 10 | untranslated-messages-file: lib/l10n/untranslated.json -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: constant_identifier_names 2 | 3 | /// App privacy policy url. 4 | String privacyPolicyUrl = 'https://pureinfoapps.com/files-tools/privacy-policy'; 5 | 6 | /// App terms and conditions url. 7 | String termsAndConditionsUrl = 8 | 'https://pureinfoapps.com/files-tools/terms-and-conditions'; 9 | 10 | /// App source code url. 11 | String sourceCodeUrl = 'https://github.com/chaudharydeepanshu/files_tools'; 12 | 13 | /// App creator github account url. 14 | String creatorGithubUrl = 'https://github.com/chaudharydeepanshu'; 15 | 16 | /// App creator linkedin account url. 17 | String creatorLinkedInUrl = 'https://www.linkedin.com/in/chaudhary-deepanshu/'; 18 | 19 | /// App icon asset name. 20 | String appIconAssetName = 'assets/app_icon_compressed.png'; 21 | 22 | /// Loading animation asset name. 23 | String loadingAnimationAssetName = 'assets/rive/finger_tapping.riv'; 24 | 25 | /// No files picked animation asset name. 26 | String noFilesPickedAnimationAssetName = 27 | 'assets/rive/impatient_placeholder.riv'; 28 | 29 | /// Success animation asset name. 30 | String successAnimationAssetName = 'assets/rive/rive_emoji_pack.riv'; 31 | 32 | /// No ads icon asset name. 33 | String noAdsAssetName = 'assets/no_ads.png'; 34 | 35 | /// Open Source icon asset name. 36 | String openSourceAssetName = 'assets/open_source.png'; 37 | 38 | /// Open Source icon asset name. 39 | String githubAssetName = 'assets/github_3d_icon.png'; 40 | 41 | /// Icons8 url. 42 | String icons8Url = 'https://icons8.com'; 43 | 44 | /// Rive community url. 45 | String riveCommunityUrl = 'https://rive.app/community/'; 46 | 47 | /// Types of formats for a file size. 48 | enum BytesFormatType { 49 | /// Auto type is used get file size in best suitable format. 50 | auto, 51 | 52 | /// B type is used get file size in Byte format. 53 | B, 54 | 55 | /// KB type is used get file size in Kilo Byte format. 56 | KB, 57 | 58 | /// MB type is used get file size in Mega Byte format. 59 | MB, 60 | 61 | /// GB type is used get file size in Giga Byte format. 62 | GB, 63 | 64 | /// TB type is used get file size in Tera Byte format. 65 | TB, 66 | 67 | /// PB type is used get file size in Peta Byte format. 68 | PB, 69 | 70 | /// EB type is used get file size in Exa Byte format. 71 | EB, 72 | 73 | /// ZB type is used get file size in Zetta Byte format. 74 | ZB, 75 | 76 | /// YB type is used get file size in Yotta Byte format. 77 | YB, 78 | } 79 | 80 | /// Types of compressions for a file. 81 | enum CompressionTypes { 82 | /// For less compression. 83 | less, 84 | 85 | /// For medium compression. 86 | medium, 87 | 88 | /// For high compression. 89 | extreme, 90 | 91 | /// For custom compression. 92 | custom, 93 | } 94 | -------------------------------------------------------------------------------- /lib/l10n/untranslated.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /lib/models/file_model.dart: -------------------------------------------------------------------------------- 1 | /// Model class for user picked files. 2 | /// 3 | /// It hold all the useful information about a user picked file. 4 | class InputFileModel { 5 | /// Defining [InputFileModel] constructor. 6 | InputFileModel({ 7 | required this.fileName, 8 | required this.fileDate, 9 | required this.fileTime, 10 | required this.fileSizeFormatBytes, 11 | required this.fileSizeBytes, 12 | required this.fileUri, 13 | }); 14 | 15 | /// User picked file name. 16 | final String fileName; 17 | 18 | /// User picked file last modified date in (DD/MM/YYYY) format. 19 | final String fileDate; 20 | 21 | /// User picked file last modified time in (hh:mm aa) format. 22 | final String fileTime; 23 | 24 | /// User picked file formatted size using [Utility.formatBytes]. 25 | final String fileSizeFormatBytes; 26 | 27 | /// User picked file size as bytes unit. 28 | final int fileSizeBytes; 29 | 30 | /// User picked file Uri from platform. 31 | final String fileUri; 32 | 33 | /// Creates copy of InputFileModel object with the given 34 | /// values replaced with new values. 35 | InputFileModel copyWith({ 36 | String? fileName, 37 | String? fileDate, 38 | String? fileTime, 39 | String? fileSizeFormatBytes, 40 | int? fileSizeBytes, 41 | String? fileUri, 42 | }) { 43 | return InputFileModel( 44 | fileName: fileName ?? this.fileName, 45 | fileDate: fileDate ?? this.fileDate, 46 | fileTime: fileTime ?? this.fileTime, 47 | fileSizeFormatBytes: fileSizeFormatBytes ?? this.fileSizeFormatBytes, 48 | fileSizeBytes: fileSizeBytes ?? this.fileSizeBytes, 49 | fileUri: fileUri ?? this.fileUri, 50 | ); 51 | } 52 | 53 | /// Overriding InputFileModel equality operator. 54 | @override 55 | bool operator ==(Object other) => 56 | identical(this, other) || 57 | other is InputFileModel && 58 | runtimeType == other.runtimeType && 59 | fileName == other.fileName && 60 | fileDate == other.fileDate && 61 | fileTime == other.fileTime && 62 | fileSizeFormatBytes == other.fileSizeFormatBytes && 63 | fileSizeBytes == other.fileSizeBytes && 64 | fileUri == other.fileUri; 65 | 66 | /// Overriding InputFileModel hashCode. 67 | @override 68 | int get hashCode => 69 | fileName.hashCode ^ 70 | fileDate.hashCode ^ 71 | fileTime.hashCode ^ 72 | fileSizeFormatBytes.hashCode ^ 73 | fileSizeBytes.hashCode ^ 74 | fileUri.hashCode; 75 | 76 | /// Overriding InputFileModel toString to make it easier to see information. 77 | /// when using the print statement. 78 | @override 79 | String toString() { 80 | return 'FileModel{' 81 | 'fileName: $fileName, ' 82 | 'fileDate: $fileDate, ' 83 | 'fileTime: $fileTime, ' 84 | 'fileSizeFormatBytes: $fileSizeFormatBytes, ' 85 | 'fileSizeBytes: $fileSizeBytes, ' 86 | 'fileUri: $fileUri ' 87 | '}'; 88 | } 89 | } 90 | 91 | /// Model class for app result files. 92 | /// 93 | /// It hold all the useful information about a user picked file. 94 | class OutputFileModel { 95 | /// Defining OutputFileModel constructor. 96 | const OutputFileModel({ 97 | required this.fileName, 98 | required this.fileDate, 99 | required this.fileTime, 100 | required this.fileSizeFormatBytes, 101 | required this.fileSizeBytes, 102 | required this.filePath, 103 | }); 104 | 105 | /// App result file name. 106 | final String fileName; 107 | 108 | /// App result file last modified date in (DD/MM/YYYY) format. 109 | final String fileDate; 110 | 111 | /// App result file last modified time in (hh:mm aa) format. 112 | final String fileTime; 113 | 114 | /// App result file formatted size using [Utility.formatBytes]. 115 | final String fileSizeFormatBytes; 116 | 117 | /// App result file size as bytes unit. 118 | final int fileSizeBytes; 119 | 120 | /// App result cached file path. 121 | final String filePath; 122 | 123 | /// Creates copy of OutputFileModel object with the given 124 | /// values replaced with new values. 125 | OutputFileModel copyWith({ 126 | String? fileName, 127 | String? fileDate, 128 | String? fileTime, 129 | String? fileSizeFormatBytes, 130 | int? fileSizeBytes, 131 | String? filePath, 132 | }) { 133 | return OutputFileModel( 134 | fileName: fileName ?? this.fileName, 135 | fileDate: fileDate ?? this.fileDate, 136 | fileTime: fileTime ?? this.fileTime, 137 | fileSizeFormatBytes: fileSizeFormatBytes ?? this.fileSizeFormatBytes, 138 | fileSizeBytes: fileSizeBytes ?? this.fileSizeBytes, 139 | filePath: filePath ?? this.filePath, 140 | ); 141 | } 142 | 143 | /// Overriding OutputFileModel equality operator. 144 | @override 145 | bool operator ==(Object other) => 146 | identical(this, other) || 147 | other is OutputFileModel && 148 | runtimeType == other.runtimeType && 149 | fileName == other.fileName && 150 | fileDate == other.fileDate && 151 | fileTime == other.fileTime && 152 | fileSizeFormatBytes == other.fileSizeFormatBytes && 153 | fileSizeBytes == other.fileSizeBytes && 154 | filePath == other.filePath; 155 | 156 | /// Overriding OutputFileModel hashCode. 157 | @override 158 | int get hashCode => 159 | fileName.hashCode ^ 160 | fileDate.hashCode ^ 161 | fileTime.hashCode ^ 162 | fileSizeFormatBytes.hashCode ^ 163 | fileSizeBytes.hashCode ^ 164 | filePath.hashCode; 165 | 166 | /// Overriding InputFileModel toString to make it easier to see information. 167 | /// when using the print statement. 168 | @override 169 | String toString() { 170 | return 'FileModel{' 171 | 'fileName: $fileName, ' 172 | 'fileDate: $fileDate, ' 173 | 'fileTime: $fileTime, ' 174 | 'fileSizeFormatBytes: $fileSizeFormatBytes, ' 175 | 'fileSizeBytes: $fileSizeBytes, ' 176 | 'filePath: $filePath ' 177 | '}'; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lib/models/image_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// Model class for an image. 4 | /// 5 | /// It holds all the information about an image. 6 | /// For example: If a image fails to load in the app then we can update the 7 | /// [imageErrorStatus] in model to use that updated status somewhere in the 8 | /// app for further actions. 9 | class ImageModel { 10 | /// Defining [ImageModel] constructor. 11 | ImageModel({ 12 | required this.imageName, 13 | required this.imageBytes, 14 | required this.imageErrorStatus, 15 | required this.imageErrorMessage, 16 | required this.imageErrorStackTrace, 17 | }); 18 | 19 | /// Image name. 20 | final String imageName; 21 | 22 | /// Image data. 23 | final Uint8List? imageBytes; 24 | 25 | /// Image error status. 26 | final bool imageErrorStatus; 27 | 28 | /// Image error message. 29 | final String imageErrorMessage; 30 | 31 | /// Image error StackTrace. 32 | final StackTrace imageErrorStackTrace; 33 | 34 | /// Creates copy of ImageModel object with the given 35 | /// values replaced with new values. 36 | ImageModel copyWith({ 37 | String? imageName, 38 | Uint8List? imageBytes, 39 | bool? imageErrorStatus, 40 | String? imageErrorMessage, 41 | StackTrace? imageErrorStackTrace, 42 | }) { 43 | return ImageModel( 44 | imageName: imageName ?? this.imageName, 45 | imageBytes: imageBytes ?? this.imageBytes, 46 | imageErrorStatus: imageErrorStatus ?? this.imageErrorStatus, 47 | imageErrorMessage: imageErrorMessage ?? this.imageErrorMessage, 48 | imageErrorStackTrace: imageErrorStackTrace ?? this.imageErrorStackTrace, 49 | ); 50 | } 51 | 52 | /// Overriding ImageModel equality operator. 53 | @override 54 | bool operator ==(Object other) => 55 | identical(this, other) || 56 | other is ImageModel && 57 | runtimeType == other.runtimeType && 58 | imageName == other.imageName && 59 | imageBytes == other.imageBytes && 60 | imageErrorStatus == other.imageErrorStatus && 61 | imageErrorMessage == other.imageErrorMessage && 62 | imageErrorStackTrace == other.imageErrorStackTrace; 63 | 64 | /// Overriding ImageModel hashCode. 65 | @override 66 | int get hashCode => 67 | imageName.hashCode ^ 68 | imageBytes.hashCode ^ 69 | imageErrorStatus.hashCode ^ 70 | imageErrorMessage.hashCode ^ 71 | imageErrorStackTrace.hashCode; 72 | 73 | /// Overriding ImageModel toString to make it easier to see information. 74 | /// when using the print statement. 75 | @override 76 | String toString() { 77 | return 'ImageModel{' 78 | 'imageName: $imageName, ' 79 | 'imageBytes: $imageBytes, ' 80 | 'imageErrorStatus: $imageErrorStatus, ' 81 | 'imageErrorMessage: $imageErrorMessage, ' 82 | 'imageErrorStackTrace: $imageErrorStackTrace' 83 | '}'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/models/pdf_page_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | /// Model class for an individual PDF page. 4 | /// 5 | /// It holds all the information about an PDF page. 6 | /// For example: If a PDF page fails to load in the app then we can update the 7 | /// [pageErrorStatus] in model to use that updated status somewhere in the 8 | /// app for further actions. 9 | class PdfPageModel { 10 | /// Defining [PdfPageModel] constructor. 11 | PdfPageModel({ 12 | required this.pageIndex, 13 | required this.pageBytes, 14 | required this.pageSelected, 15 | required this.pageRotationAngle, 16 | required this.pageHidden, 17 | required this.pageErrorStatus, 18 | }); 19 | 20 | /// PDF page index. 21 | final int pageIndex; 22 | 23 | /// PDF page data. 24 | final Uint8List? pageBytes; 25 | 26 | /// PDF page selection status. 27 | final bool pageSelected; 28 | 29 | /// PDF page rotation angle. 30 | final int pageRotationAngle; 31 | 32 | /// PDF page hidden status. 33 | final bool pageHidden; 34 | 35 | /// PDF page error status. 36 | final bool pageErrorStatus; 37 | 38 | /// Creates copy of PdfPageModel object with the given 39 | /// values replaced with new values. 40 | PdfPageModel copyWith({ 41 | int? pageIndex, 42 | Uint8List? pageBytes, 43 | bool? pageSelected, 44 | int? pageRotationAngle, 45 | bool? pageHidden, 46 | bool? pageErrorStatus, 47 | }) { 48 | return PdfPageModel( 49 | pageIndex: pageIndex ?? this.pageIndex, 50 | pageBytes: pageBytes ?? this.pageBytes, 51 | pageSelected: pageSelected ?? this.pageSelected, 52 | pageRotationAngle: pageRotationAngle ?? this.pageRotationAngle, 53 | pageHidden: pageHidden ?? this.pageHidden, 54 | pageErrorStatus: pageErrorStatus ?? this.pageErrorStatus, 55 | ); 56 | } 57 | 58 | /// Overriding PdfPageModel equality operator. 59 | @override 60 | bool operator ==(Object other) => 61 | identical(this, other) || 62 | other is PdfPageModel && 63 | runtimeType == other.runtimeType && 64 | pageIndex == other.pageIndex && 65 | pageBytes == other.pageBytes && 66 | pageHidden == other.pageHidden && 67 | pageSelected == other.pageSelected && 68 | pageRotationAngle == other.pageRotationAngle && 69 | pageErrorStatus == other.pageErrorStatus; 70 | 71 | /// Overriding PdfPageModel hashCode. 72 | @override 73 | int get hashCode => 74 | pageIndex.hashCode ^ 75 | pageBytes.hashCode ^ 76 | pageHidden.hashCode ^ 77 | pageSelected.hashCode ^ 78 | pageRotationAngle.hashCode ^ 79 | pageErrorStatus.hashCode; 80 | 81 | /// Overriding PdfPageModel toString to make it easier to see information. 82 | /// when using the print statement. 83 | @override 84 | String toString() { 85 | return 'PdfPageModel{' 86 | 'pageIndex: $pageIndex, ' 87 | 'pageBytes: $pageBytes, ' 88 | 'pageErrorStatus: $pageErrorStatus, ' 89 | 'pageSelected: $pageSelected, ' 90 | 'pageRotationAngle: $pageRotationAngle, ' 91 | 'pageHidden: $pageHidden' 92 | '}'; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/models/tool_actions_model.dart: -------------------------------------------------------------------------------- 1 | /// Model class for an tool action. 2 | /// 3 | /// It holds action name and action onTap function for a tool action. 4 | /// For example: A tool 'Split PDF' consists actions such as 'Split by Size', 5 | /// 'Split by Page Count', etc. 6 | class ToolActionModel { 7 | /// Defining [ToolActionModel] constructor. 8 | ToolActionModel({ 9 | required this.actionText, 10 | required this.actionOnTap, 11 | }); 12 | 13 | /// Tool action name. 14 | final String actionText; 15 | 16 | /// Tool action name. 17 | final void Function()? actionOnTap; 18 | 19 | /// Overriding ToolActionModel equality operator. 20 | @override 21 | bool operator ==(Object other) => 22 | identical(this, other) || 23 | other is ToolActionModel && 24 | runtimeType == other.runtimeType && 25 | actionText == other.actionText && 26 | actionOnTap == other.actionOnTap; 27 | 28 | /// Overriding ToolActionModel hashCode. 29 | @override 30 | int get hashCode => actionText.hashCode ^ actionOnTap.hashCode; 31 | 32 | /// Overriding ToolActionModel toString to make it easier to see information. 33 | /// when using the print statement. 34 | @override 35 | String toString() { 36 | return 'ToolActionModel{' 37 | 'actionText: $actionText, ' 38 | 'actionOnTap: $actionOnTap' 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/state/package_info_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:package_info_plus/package_info_plus.dart'; 2 | 3 | /// App [PackageInfo] Instance. 4 | late final PackageInfo packageInfo; 5 | 6 | /// App [PackageInfo] state class. 7 | class AppPackageInfo { 8 | /// [PackageInfo] Instance initializer. 9 | static Future initPackageInfo() async { 10 | packageInfo = await PackageInfo.fromPlatform(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/state/preferences.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/main.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | /// App SharedPreferences Instance. 6 | late final SharedPreferences sharedPreferencesInstance; 7 | 8 | /// App SharedPreferences state class. 9 | class Preferences { 10 | /// Theme mode SharedPref key. 11 | static String themeModePerfKey = 'themeMode'; 12 | 13 | /// OnBoarding status SharedPref key. 14 | static String onBoardingStatusPerfKey = 'isUserOnBoarded'; 15 | 16 | /// User theme seed color SharedPref key. 17 | static String userThemeSeedColorValuePerfKey = 'userThemeSeedColorValue'; 18 | 19 | /// Dynamic theme enable status SharedPref key. 20 | static String dynamicThemeStatusPerfKey = 'dynamicThemeStatus'; 21 | 22 | /// Crashlytics Collection enable status SharedPref key. 23 | static String crashlyticsCollectionStatusPerfKey = 24 | 'crashlyticsCollectionStatus'; 25 | 26 | /// Analytics Collection enable status SharedPref key. 27 | static String analyticsCollectionStatusPerfKey = 'analyticsCollectionStatus'; 28 | 29 | /// SharedPreferences Instance initializer. 30 | static Future initSharedPreferences() async { 31 | sharedPreferencesInstance = await SharedPreferences.getInstance(); 32 | } 33 | 34 | /// For persisting theme mode status in SharedPreferences. 35 | static Future persistThemeMode(final ThemeMode mode) => 36 | sharedPreferencesInstance.setString(themeModePerfKey, mode.toString()); 37 | 38 | /// For getting theme mode status persisted from SharedPreferences. 39 | static ThemeMode get themeMode => ThemeMode.values.firstWhere( 40 | (final ThemeMode element) => 41 | element.toString() == 42 | sharedPreferencesInstance.getString(themeModePerfKey), 43 | orElse: () => ThemeMode.light, 44 | ); 45 | 46 | /// For persisting on boarding status in SharedPreferences. 47 | static Future persistOnBoardingStatus(final bool isUserOnBoarded) => 48 | sharedPreferencesInstance.setBool( 49 | onBoardingStatusPerfKey, 50 | isUserOnBoarded, 51 | ); 52 | 53 | /// For getting on boarding status persisted from SharedPreferences. 54 | static bool get isUserOnBoarded => 55 | sharedPreferencesInstance.getBool(onBoardingStatusPerfKey) ?? false; 56 | 57 | /// For persisting user theme seed color in SharedPreferences. 58 | static Future persistUserThemeSeedColorValue( 59 | final int userThemeSeedColorValue, 60 | ) => 61 | sharedPreferencesInstance.setInt( 62 | userThemeSeedColorValuePerfKey, 63 | userThemeSeedColorValue, 64 | ); 65 | 66 | /// For getting on boarding status persisted from SharedPreferences. 67 | static int get userThemeSeedColorValue => 68 | sharedPreferencesInstance.getInt(userThemeSeedColorValuePerfKey) ?? 69 | const Color(0xFFA93054).value; 70 | 71 | /// For persisting dynamic theme status in SharedPreferences. 72 | static Future persistDynamicThemeStatus( 73 | final bool dynamicThemeStatus, 74 | ) => 75 | sharedPreferencesInstance.setBool( 76 | dynamicThemeStatusPerfKey, 77 | dynamicThemeStatus, 78 | ); 79 | 80 | /// For getting dynamic theme status from SharedPreferences. 81 | static bool get dynamicThemeStatus => 82 | sharedPreferencesInstance.getBool(dynamicThemeStatusPerfKey) ?? true; 83 | 84 | /// For persisting crashlytics collection status in SharedPreferences. 85 | static Future persistCrashlyticsCollectionStatus( 86 | final bool crashlyticsCollectionStatus, 87 | ) async { 88 | await crashlyticsInstance.setCrashlyticsCollectionEnabled( 89 | crashlyticsCollectionStatus, 90 | ); 91 | return sharedPreferencesInstance.setBool( 92 | crashlyticsCollectionStatusPerfKey, 93 | crashlyticsCollectionStatus, 94 | ); 95 | } 96 | 97 | /// For getting crashlytics collection status from SharedPreferences. 98 | static bool get crashlyticsCollectionStatus => 99 | sharedPreferencesInstance.getBool(crashlyticsCollectionStatusPerfKey) ?? 100 | true; 101 | 102 | /// For persisting analytics collection status in SharedPreferences. 103 | static Future persistAnalyticsCollectionStatus( 104 | final bool analyticsCollectionStatus, 105 | ) async { 106 | await analyticsInstance.setAnalyticsCollectionEnabled( 107 | analyticsCollectionStatus, 108 | ); 109 | return sharedPreferencesInstance.setBool( 110 | analyticsCollectionStatusPerfKey, 111 | analyticsCollectionStatus, 112 | ); 113 | } 114 | 115 | /// For getting analytics collection status from SharedPreferences. 116 | static bool get analyticsCollectionStatus => 117 | sharedPreferencesInstance.getBool(analyticsCollectionStatusPerfKey) ?? 118 | true; 119 | } 120 | -------------------------------------------------------------------------------- /lib/state/providers.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/state/app_theme_state.dart'; 2 | import 'package:files_tools/state/tools_actions_state.dart'; 3 | import 'package:files_tools/state/tools_screens_state.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | 6 | /// App theme state provider. 7 | final ChangeNotifierProvider appThemeStateProvider = 8 | ChangeNotifierProvider( 9 | (final ChangeNotifierProviderRef ref) => 10 | AppThemeState()..initTheme(), 11 | ); 12 | 13 | /// App tools screens state provider. 14 | final AutoDisposeChangeNotifierProvider 15 | toolsScreensStateProvider = ChangeNotifierProvider.autoDispose( 16 | (final AutoDisposeChangeNotifierProviderRef ref) => 17 | ToolsScreensState(), 18 | ); 19 | 20 | /// App tools actions screens state provider. 21 | final AutoDisposeChangeNotifierProvider 22 | toolsActionsStateProvider = ChangeNotifierProvider.autoDispose( 23 | (final AutoDisposeChangeNotifierProviderRef ref) => 24 | ToolsActionsState(), 25 | ); 26 | -------------------------------------------------------------------------------- /lib/ui/components/color_picker.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:flex_color_picker/flex_color_picker.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | /// Define custom colors. The 'guide' color values are from 6 | /// https://material.io/design/color/the-color-system.html#color-theme-creation 7 | 8 | /// Guide primary color. 9 | const Color guidePrimary = Color(0xFF6200EE); 10 | 11 | /// Guide primary variant color. 12 | const Color guidePrimaryVariant = Color(0xFF3700B3); 13 | 14 | /// Guide secondary color. 15 | const Color guideSecondary = Color(0xFF03DAC6); 16 | 17 | /// Guide secondary variant color. 18 | const Color guideSecondaryVariant = Color(0xFF018786); 19 | 20 | /// Guide error color. 21 | const Color guideError = Color(0xFFB00020); 22 | 23 | /// Guide dark error color. 24 | const Color guideErrorDark = Color(0xFFCF6679); 25 | 26 | /// Blue blues color. 27 | const Color blueBlues = Color(0xFF174378); 28 | 29 | /// Make a custom ColorSwatch to name map from the above custom colors. 30 | final Map, String> colorsNameMap = 31 | , String>{ 32 | ColorTools.createPrimarySwatch(guidePrimary): 'Guide Purple', 33 | ColorTools.createPrimarySwatch(guidePrimaryVariant): 'Guide Purple Variant', 34 | ColorTools.createAccentSwatch(guideSecondary): 'Guide Teal', 35 | ColorTools.createAccentSwatch(guideSecondaryVariant): 'Guide Teal Variant', 36 | ColorTools.createPrimarySwatch(guideError): 'Guide Error', 37 | ColorTools.createPrimarySwatch(guideErrorDark): 'Guide Error Dark', 38 | ColorTools.createPrimarySwatch(blueBlues): 'Blue blues', 39 | }; 40 | 41 | /// Dialog for color picking in app. 42 | Future colorPickerDialog({ 43 | required final BuildContext context, 44 | required final Color dialogPickerColor, 45 | required final ValueChanged onColorChanged, 46 | }) async { 47 | AppLocale appLocale = AppLocale.of(context); 48 | String heading = appLocale.colorPicker_Heading; 49 | String subheading = appLocale.colorPicker_Subheading; 50 | String wheelSubheading = appLocale.colorPicker_WheelSubheading; 51 | 52 | return ColorPicker( 53 | // Use the dialogPickerColor as start color. 54 | color: dialogPickerColor, 55 | // Update the dialogPickerColor using the callback. 56 | onColorChanged: (final Color color) => onColorChanged.call(color), 57 | borderRadius: 4, 58 | spacing: 5, 59 | runSpacing: 5, 60 | wheelDiameter: 155, 61 | heading: Text( 62 | heading, 63 | style: Theme.of(context).textTheme.titleMedium, 64 | ), 65 | subheading: Text( 66 | subheading, 67 | style: Theme.of(context).textTheme.titleMedium, 68 | ), 69 | wheelSubheading: Text( 70 | wheelSubheading, 71 | style: Theme.of(context).textTheme.titleMedium, 72 | ), 73 | showMaterialName: true, 74 | showColorName: true, 75 | showColorCode: true, 76 | copyPasteBehavior: const ColorPickerCopyPasteBehavior( 77 | longPressMenu: true, 78 | ), 79 | materialNameTextStyle: Theme.of(context).textTheme.bodySmall, 80 | colorNameTextStyle: Theme.of(context).textTheme.bodySmall, 81 | colorCodeTextStyle: Theme.of(context).textTheme.bodySmall, 82 | pickersEnabled: const { 83 | ColorPickerType.both: false, 84 | ColorPickerType.primary: true, 85 | ColorPickerType.accent: true, 86 | ColorPickerType.bw: false, 87 | ColorPickerType.custom: true, 88 | ColorPickerType.wheel: true, 89 | }, 90 | customColorSwatchesAndNames: colorsNameMap, 91 | ).showPickerDialog( 92 | context, 93 | constraints: 94 | const BoxConstraints(minHeight: 460, minWidth: 300, maxWidth: 320), 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /lib/ui/components/crashlytics_analytics_switch.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/main.dart'; 3 | import 'package:files_tools/state/preferences.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | /// [SwitchListTile] for enabling or disabling crashlytics data collection. 7 | class CrashlyticsSwitchTile extends StatefulWidget { 8 | /// Defining [CrashlyticsSwitchTile] constructor. 9 | const CrashlyticsSwitchTile({Key? key}) : super(key: key); 10 | 11 | @override 12 | State createState() => _CrashlyticsSwitchTileState(); 13 | } 14 | 15 | class _CrashlyticsSwitchTileState extends State { 16 | bool isCrashlyticsEnabled = 17 | crashlyticsInstance.isCrashlyticsCollectionEnabled; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | AppLocale appLocale = AppLocale.of(context); 22 | String crashlyticsListTileTitle = 23 | appLocale.settings_UsageAndDiagnostics_Crashlytics_ListTileTitle; 24 | String crashlyticsListTileSubtitle = 25 | appLocale.settings_UsageAndDiagnostics_Crashlytics_ListTileSubtitle; 26 | 27 | return SwitchListTile( 28 | title: Text(crashlyticsListTileTitle), 29 | subtitle: Text( 30 | crashlyticsListTileSubtitle, 31 | ), 32 | secondary: const Icon(Icons.bug_report), 33 | value: isCrashlyticsEnabled, 34 | onChanged: (bool? value) async { 35 | await Preferences.persistCrashlyticsCollectionStatus( 36 | !isCrashlyticsEnabled, 37 | ); 38 | setState(() { 39 | isCrashlyticsEnabled = 40 | crashlyticsInstance.isCrashlyticsCollectionEnabled; 41 | }); 42 | }, 43 | ); 44 | } 45 | } 46 | 47 | /// [SwitchListTile] for enabling or disabling analytics data collection. 48 | class AnalyticsSwitchTile extends StatefulWidget { 49 | /// Defining [AnalyticsSwitchTile] constructor. 50 | const AnalyticsSwitchTile({Key? key}) : super(key: key); 51 | 52 | @override 53 | State createState() => _AnalyticsSwitchTileState(); 54 | } 55 | 56 | class _AnalyticsSwitchTileState extends State { 57 | bool isAnalyticsEnabled = Preferences.analyticsCollectionStatus; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | AppLocale appLocale = AppLocale.of(context); 62 | String analyticsListTileTitle = 63 | appLocale.settings_UsageAndDiagnostics_Analytics_ListTileTitle; 64 | String analyticsListTileSubtitle = 65 | appLocale.settings_UsageAndDiagnostics_Analytics_ListTileSubtitle; 66 | 67 | return SwitchListTile( 68 | title: Text(analyticsListTileTitle), 69 | subtitle: Text( 70 | analyticsListTileSubtitle, 71 | ), 72 | secondary: const Icon(Icons.analytics), 73 | value: isAnalyticsEnabled, 74 | onChanged: (bool? value) async { 75 | await Preferences.persistAnalyticsCollectionStatus(!isAnalyticsEnabled); 76 | setState(() { 77 | isAnalyticsEnabled = Preferences.analyticsCollectionStatus; 78 | }); 79 | }, 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/ui/components/custom_keep_alive.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// For saving the state of loaded PgeViews to avoid loading them again. 4 | class CustomKeepAlive extends StatefulWidget { 5 | /// Defining [CustomKeepAlive] constructor. 6 | const CustomKeepAlive({super.key, required this.widget}); 7 | 8 | /// Widget to be kept alive. 9 | final Widget widget; 10 | 11 | @override 12 | State createState() => _CustomKeepAliveState(); 13 | } 14 | 15 | class _CustomKeepAliveState extends State 16 | with AutomaticKeepAliveClientMixin { 17 | @override 18 | bool get wantKeepAlive => true; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | super.build(context); 23 | return widget.widget; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/ui/components/custom_snack_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Custom SnackBar which is used through out the app. 4 | ScaffoldFeatureController? showCustomSnackBar({ 5 | String? contentText, 6 | Color? backgroundColor, 7 | Duration? duration, 8 | IconData? iconData, 9 | Color? iconAndTextColor, 10 | TextStyle? textStyle, 11 | required BuildContext context, 12 | }) { 13 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 14 | 15 | if (contentText != null) { 16 | return ScaffoldMessenger.of(context).showSnackBar( 17 | SnackBar( 18 | content: Column( 19 | mainAxisSize: MainAxisSize.min, 20 | children: [ 21 | Row( 22 | children: [ 23 | Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Container( 27 | decoration: BoxDecoration( 28 | borderRadius: BorderRadius.circular(1000), 29 | border: Border.all( 30 | color: iconAndTextColor ?? 31 | Theme.of(context).colorScheme.surface, 32 | ), 33 | ), 34 | child: Padding( 35 | padding: const EdgeInsets.all(5.0), 36 | child: Center( 37 | child: Icon( 38 | iconData ?? Icons.info, 39 | color: iconAndTextColor ?? 40 | Theme.of(context).colorScheme.surface, 41 | ), 42 | ), 43 | ), 44 | ), 45 | ], 46 | ), 47 | const SizedBox( 48 | width: 10, 49 | ), 50 | Expanded( 51 | child: Text( 52 | contentText, 53 | style: textStyle != null 54 | ? textStyle.copyWith( 55 | color: iconAndTextColor ?? 56 | Theme.of(context).colorScheme.surface, 57 | ) 58 | : const TextStyle().copyWith( 59 | color: iconAndTextColor ?? 60 | Theme.of(context).colorScheme.surface, 61 | ), 62 | ), 63 | ), 64 | ], 65 | ), 66 | ], 67 | ), 68 | backgroundColor: backgroundColor, 69 | duration: duration ?? const Duration(seconds: 4), 70 | behavior: SnackBarBehavior.floating, 71 | padding: const EdgeInsets.all(8.0), 72 | margin: const EdgeInsets.all(8.0), 73 | // action: SnackBarAction( 74 | // label: 'Ok', 75 | // onPressed: () {}, 76 | // ), 77 | ), 78 | ); 79 | } else { 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/ui/components/drawer.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/constants.dart'; 2 | import 'package:files_tools/l10n/generated/app_locale.dart'; 3 | import 'package:files_tools/route/app_routes.dart' as route; 4 | import 'package:files_tools/state/package_info_state.dart'; 5 | import 'package:files_tools/ui/components/link_button.dart'; 6 | import 'package:files_tools/ui/components/theme_mode_switcher.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 9 | 10 | /// App drawer. 11 | class AppDrawer extends StatelessWidget { 12 | /// Defining [AppDrawer] constructor. 13 | const AppDrawer({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | AppLocale appLocale = AppLocale.of(context); 18 | String settings = appLocale.settings_ScreenTitle; 19 | String aboutUs = appLocale.aboutUs_ScreenTitle; 20 | String privacyPolicy = appLocale.button_PrivacyPolicy; 21 | String termsAndConditions = appLocale.button_TermsAndConditions; 22 | 23 | return Drawer( 24 | child: Stack( 25 | children: [ 26 | Column( 27 | mainAxisSize: MainAxisSize.min, 28 | children: [ 29 | Flexible( 30 | child: Consumer( 31 | builder: 32 | (BuildContext context, WidgetRef ref, Widget? child) { 33 | return DrawerHeader( 34 | decoration: BoxDecoration( 35 | color: Theme.of(context).colorScheme.surface, 36 | border: const Border(), 37 | ), 38 | child: Row( 39 | mainAxisAlignment: MainAxisAlignment.center, 40 | children: [ 41 | Column( 42 | children: [ 43 | Flexible( 44 | child: Image.asset( 45 | appIconAssetName, 46 | ), 47 | ), 48 | Text( 49 | packageInfo.appName, 50 | style: Theme.of(context).textTheme.titleLarge, 51 | ), 52 | ], 53 | ), 54 | ], 55 | ), 56 | ); 57 | }, 58 | ), 59 | ), 60 | Expanded( 61 | child: ListView( 62 | // Important: Remove any padding from the ListView. 63 | padding: EdgeInsets.zero, 64 | children: [ 65 | const ThemeModeSwitcher(), 66 | const Divider(indent: 16, endIndent: 16), 67 | ListTile( 68 | leading: const Icon(Icons.settings), 69 | title: Text(settings), 70 | onTap: () { 71 | Navigator.pushNamed( 72 | context, 73 | route.AppRoutes.settingsPage, 74 | ); 75 | }, 76 | ), 77 | ListTile( 78 | leading: const Icon(Icons.help), 79 | title: Text(aboutUs), 80 | onTap: () { 81 | Navigator.pushNamed( 82 | context, 83 | route.AppRoutes.aboutPage, 84 | ); 85 | }, 86 | ), 87 | const SizedBox( 88 | height: 48, 89 | ), 90 | ], 91 | ), 92 | ), 93 | ], 94 | ), 95 | Column( 96 | mainAxisAlignment: MainAxisAlignment.end, 97 | children: [ 98 | Container( 99 | color: Theme.of(context).colorScheme.surface, 100 | child: Column( 101 | children: [ 102 | const Divider(height: 0), 103 | Padding( 104 | padding: const EdgeInsets.symmetric( 105 | vertical: 8.0, 106 | horizontal: 5, 107 | ), 108 | child: Row( 109 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 110 | children: [ 111 | Flexible( 112 | child: LinkButton( 113 | urlLabel: privacyPolicy, 114 | urlIcon: Icons.privacy_tip, 115 | url: privacyPolicyUrl, 116 | ), 117 | ), 118 | const Text( 119 | ' - ', 120 | ), 121 | Flexible( 122 | child: LinkButton( 123 | urlLabel: termsAndConditions, 124 | urlIcon: Icons.gavel, 125 | url: termsAndConditionsUrl, 126 | ), 127 | ), 128 | ], 129 | ), 130 | ), 131 | ], 132 | ), 133 | ), 134 | ], 135 | ), 136 | ], 137 | ), 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/ui/components/dynamic_theme_switch_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/state/app_theme_state.dart'; 3 | import 'package:files_tools/state/providers.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | /// [SwitchListTile] for switching between non dynamic and dynamic themes. 8 | class DynamicThemeSwitchTile extends StatelessWidget { 9 | /// Defining [DynamicThemeSwitchTile] constructor. 10 | const DynamicThemeSwitchTile({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | AppLocale appLocale = AppLocale.of(context); 15 | String dynamicThemeListTileTitle = 16 | appLocale.settings_Theming_DynamicTheme_ListTileTitle; 17 | String dynamicThemeListTileSubtitle = 18 | appLocale.settings_Theming_DynamicTheme_ListTileSubtitle; 19 | 20 | return Consumer( 21 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 22 | bool isDynamicThemeEnabled = ref.watch( 23 | appThemeStateProvider 24 | .select((AppThemeState value) => value.isDynamicThemeEnabled), 25 | ); 26 | ColorScheme? lightDynamicColorScheme = ref.watch( 27 | appThemeStateProvider 28 | .select((AppThemeState value) => value.lightDynamicColorScheme), 29 | ); 30 | 31 | return lightDynamicColorScheme != null 32 | ? SwitchListTile( 33 | title: Text(dynamicThemeListTileTitle), 34 | subtitle: Text(dynamicThemeListTileSubtitle), 35 | secondary: const Icon(Icons.wallpaper), 36 | value: isDynamicThemeEnabled, 37 | onChanged: (bool? value) { 38 | ref.read(appThemeStateProvider).updateDynamicThemeStatus(); 39 | }, 40 | ) 41 | : const SizedBox(); 42 | }, 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/components/levitating_options_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Shows a levitating bottom app bar. 4 | /// 5 | /// We use it in the app inside a stack with a align widget having alignment 6 | /// set to bottom center. To show options like rotate, flip, process, etc. 7 | class LevitatingOptionsBar extends StatelessWidget { 8 | /// Defining [FileTile] constructor. 9 | const LevitatingOptionsBar({Key? key, required this.optionsList}) 10 | : super(key: key); 11 | 12 | /// List of widgets of options. 13 | final List optionsList; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Padding( 18 | padding: const EdgeInsets.only(bottom: 30.0, left: 30, right: 30), 19 | child: ClipRRect( 20 | borderRadius: BorderRadius.circular(1000), 21 | child: BottomAppBar( 22 | padding: EdgeInsets.zero, 23 | child: SizedBox( 24 | height: 70, 25 | child: Row( 26 | children: optionsList, 27 | ), 28 | ), 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/ui/components/link_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/utils/utility.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// For showing a link button between a continuous long text. 5 | class LinkButton extends StatelessWidget { 6 | /// Defining [LinkButton] constructor. 7 | const LinkButton({ 8 | Key? key, 9 | required this.urlLabel, 10 | required this.url, 11 | this.urlIcon, 12 | }) : super(key: key); 13 | 14 | /// Link button text. 15 | final String urlLabel; 16 | 17 | /// Link button icon. 18 | final IconData? urlIcon; 19 | 20 | /// Link url. 21 | final String url; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return urlIcon != null 26 | ? TextButton.icon( 27 | style: TextButton.styleFrom( 28 | padding: EdgeInsets.zero, 29 | shape: RoundedRectangleBorder( 30 | borderRadius: BorderRadius.circular(0), 31 | ), 32 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 33 | visualDensity: VisualDensity.compact, 34 | minimumSize: const Size(0, 0), 35 | textStyle: Theme.of(context).textTheme.bodySmall, 36 | ), 37 | onPressed: () { 38 | Utility.urlLauncher(url); 39 | }, 40 | icon: Icon( 41 | urlIcon, 42 | size: Theme.of(context).textTheme.bodySmall?.fontSize, 43 | ), 44 | label: Text( 45 | urlLabel, 46 | textAlign: TextAlign.center, 47 | ), 48 | ) 49 | : TextButton( 50 | style: TextButton.styleFrom( 51 | padding: EdgeInsets.zero, 52 | shape: RoundedRectangleBorder( 53 | borderRadius: BorderRadius.circular(0), 54 | ), 55 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 56 | visualDensity: VisualDensity.compact, 57 | minimumSize: const Size(0, 0), 58 | textStyle: Theme.of(context).textTheme.bodySmall, 59 | ), 60 | onPressed: () { 61 | Utility.urlLauncher(url); 62 | }, 63 | child: Text(urlLabel), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/ui/components/loading.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/constants.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:rive/rive.dart'; 4 | 5 | /// Widget for showing an loading animation using [RiveAnimation]. 6 | class LoadingIndicator extends StatelessWidget { 7 | /// Defining [LoadingIndicator] constructor. 8 | const LoadingIndicator({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return SizedBox( 13 | width: 150, 14 | height: 100, 15 | child: RiveAnimation.asset( 16 | loadingAnimationAssetName, 17 | fit: BoxFit.contain, 18 | ), 19 | ); 20 | } 21 | } 22 | 23 | /// Widget for showing loading state. 24 | class Loading extends StatelessWidget { 25 | /// Defining [Loading] constructor. 26 | const Loading({Key? key, required this.loadingText}) : super(key: key); 27 | 28 | /// Loading description text. 29 | final String loadingText; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Center( 34 | child: Column( 35 | mainAxisAlignment: MainAxisAlignment.center, 36 | children: [ 37 | const LoadingIndicator(), 38 | Text(loadingText, style: Theme.of(context).textTheme.bodySmall), 39 | ], 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/ui/components/more_info_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Widget for showing info through a popup about a something anywhere. 4 | class MoreInfoButton extends StatelessWidget { 5 | /// Defining [MoreInfoButton] constructor. 6 | const MoreInfoButton({Key? key, required this.infoText}) : super(key: key); 7 | 8 | /// Information text. 9 | final String infoText; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Tooltip( 14 | message: infoText, 15 | triggerMode: TooltipTriggerMode.tap, 16 | child: TextButton( 17 | style: TextButton.styleFrom( 18 | padding: EdgeInsets.zero, 19 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 20 | ), 21 | onPressed: null, 22 | child: const Icon(Icons.info), 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/components/reset_app_theme_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/state/preferences.dart'; 3 | import 'package:files_tools/state/providers.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | /// Widget for showing reset button to reset app theme settings. 8 | class ResetAppThemeSettings extends StatelessWidget { 9 | /// Defining [ResetAppThemeSettings] constructor. 10 | const ResetAppThemeSettings({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | AppLocale appLocale = AppLocale.of(context); 15 | String resetTheme = appLocale.button_ResetTheme; 16 | 17 | return Consumer( 18 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 19 | return TextButton( 20 | style: TextButton.styleFrom( 21 | padding: EdgeInsets.zero, 22 | tapTargetSize: MaterialTapTargetSize.shrinkWrap, 23 | ), 24 | onPressed: () async { 25 | sharedPreferencesInstance.remove(Preferences.themeModePerfKey); 26 | sharedPreferencesInstance 27 | .remove(Preferences.userThemeSeedColorValuePerfKey); 28 | sharedPreferencesInstance 29 | .remove(Preferences.dynamicThemeStatusPerfKey); 30 | // sharedPreferencesInstance 31 | // .remove(Preferences.onBoardingStatusPerfKey); 32 | ref.read(appThemeStateProvider).updateTheme(); 33 | }, 34 | child: Text(resetTheme), 35 | ); 36 | }, 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/ui/components/theme_chooser_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/state/app_theme_state.dart'; 3 | import 'package:files_tools/state/providers.dart'; 4 | import 'package:files_tools/ui/components/color_picker.dart'; 5 | import 'package:flex_color_picker/flex_color_picker.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | 9 | /// Widget for changing app theme color scheme. 10 | class ThemeChooserWidget extends StatelessWidget { 11 | /// Defining [ThemeChooserWidget] constructor. 12 | const ThemeChooserWidget({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | AppLocale appLocale = AppLocale.of(context); 17 | String chooseThemeColorListTileTitle = 18 | appLocale.settings_Theming_ChooseThemeColor_ListTileTitle; 19 | 20 | return Consumer( 21 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 22 | Color userColorSchemeSeedColor = ref.watch( 23 | appThemeStateProvider 24 | .select((AppThemeState value) => value.userColorSchemeSeedColor), 25 | ); 26 | return ListTile( 27 | title: Text(chooseThemeColorListTileTitle), 28 | subtitle: Text( 29 | '${ColorTools.materialNameAndCode(userColorSchemeSeedColor)} ' 30 | 'aka ' 31 | '${ColorTools.nameThatColor(userColorSchemeSeedColor)}', 32 | ), 33 | leading: const Icon(Icons.palette), 34 | trailing: ColorIndicator( 35 | width: 44, 36 | height: 44, 37 | borderRadius: 22, 38 | color: userColorSchemeSeedColor, 39 | ), 40 | onTap: () async { 41 | // Store current color before we open the dialog. 42 | final Color colorBeforeDialog = userColorSchemeSeedColor; 43 | // Wait for the picker to close, if dialog was dismissed, 44 | // then restore the color we had before it was opened. 45 | bool dialogStatus = await colorPickerDialog( 46 | context: context, 47 | dialogPickerColor: userColorSchemeSeedColor, 48 | onColorChanged: (Color value) { 49 | ref.read(appThemeStateProvider).updateUserTheme(value); 50 | }, 51 | ); 52 | 53 | if (!dialogStatus) { 54 | ref 55 | .read(appThemeStateProvider) 56 | .updateUserTheme(colorBeforeDialog); 57 | } 58 | }, 59 | ); 60 | }, 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/ui/components/theme_mode_switcher.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/state/app_theme_state.dart'; 3 | import 'package:files_tools/state/providers.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 6 | 7 | /// Widget for changing app theme mode to light, dart and system. 8 | class ThemeModeSwitcher extends StatelessWidget { 9 | /// Defining [ThemeModeSwitcher] constructor. 10 | const ThemeModeSwitcher({Key? key}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | AppLocale appLocale = AppLocale.of(context); 15 | String themeModeListTileTitle = 16 | appLocale.settings_Theming_ThemeMode_ListTileTitle; 17 | String themeModeLightListTileSubtitle = 18 | appLocale.settings_Theming_ThemeMode_Light_ListTileSubtitle; 19 | String themeModeDarkListTileSubtitle = 20 | appLocale.settings_Theming_ThemeMode_Dark_ListTileSubtitle; 21 | String themeModeSystemListTileSubtitle = 22 | appLocale.settings_Theming_ThemeMode_System_ListTileSubtitle; 23 | 24 | return Consumer( 25 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 26 | ThemeMode themeMode = ref.watch( 27 | appThemeStateProvider 28 | .select((AppThemeState value) => value.themeMode), 29 | ); 30 | String buttonText = themeMode == ThemeMode.light 31 | ? themeModeLightListTileSubtitle 32 | : themeMode == ThemeMode.dark 33 | ? themeModeDarkListTileSubtitle 34 | : themeModeSystemListTileSubtitle; 35 | IconData iconData = themeMode == ThemeMode.light 36 | ? Icons.light_mode 37 | : themeMode == ThemeMode.dark 38 | ? Icons.dark_mode 39 | : Icons.android; 40 | return ListTile( 41 | leading: Icon(iconData), 42 | title: Text(themeModeListTileTitle), 43 | subtitle: Text(buttonText), 44 | onTap: () { 45 | ref.read(appThemeStateProvider).updateThemeMode(); 46 | }, 47 | ); 48 | }, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/ui/components/tool_actions_section.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/models/tool_actions_model.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Widget for showing all tools actions buttons. 5 | class ToolActionsCard extends StatelessWidget { 6 | /// Defining [ToolActionsCard] constructor. 7 | const ToolActionsCard({Key? key, required this.toolActions}) 8 | : super(key: key); 9 | 10 | /// Takes tools actions models list. 11 | final List toolActions; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Card( 16 | clipBehavior: Clip.antiAlias, 17 | margin: const EdgeInsets.symmetric(horizontal: 16.0), 18 | child: Padding( 19 | padding: const EdgeInsets.all(16.0), 20 | child: Column( 21 | children: [ 22 | const Icon(Icons.looks_two), 23 | const Divider(), 24 | ConstrainedBox( 25 | constraints: const BoxConstraints(maxHeight: 300), 26 | child: ListView.separated( 27 | physics: const NeverScrollableScrollPhysics(), 28 | shrinkWrap: true, 29 | itemBuilder: (BuildContext context, int index) { 30 | return FilledButton( 31 | onPressed: toolActions[index].actionOnTap, 32 | child: Text( 33 | toolActions[index].actionText, 34 | textAlign: TextAlign.center, 35 | ), 36 | ); 37 | }, 38 | separatorBuilder: (BuildContext context, int index) { 39 | return const SizedBox(height: 10); 40 | }, 41 | itemCount: toolActions.length, 42 | ), 43 | ), 44 | ], 45 | ), 46 | ), 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/ui/components/tools_about_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// Widget for showing about or info card in actions screen. 5 | class AboutActionCard extends StatelessWidget { 6 | /// Defining [AboutActionCard] constructor. 7 | const AboutActionCard({ 8 | Key? key, 9 | required this.aboutTitle, 10 | this.aboutBody, 11 | this.aboutBodyTitle, 12 | }) : super(key: key); 13 | 14 | /// About card title text. 15 | final String aboutTitle; 16 | 17 | /// About card body text. 18 | final String? aboutBody; 19 | 20 | /// About card body title text. 21 | final String? aboutBodyTitle; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | AppLocale appLocale = AppLocale.of(context); 26 | String toolInfo = appLocale.tool_InfoCardTitle; 27 | 28 | return Card( 29 | clipBehavior: Clip.antiAlias, 30 | margin: const EdgeInsets.symmetric(horizontal: 16.0), 31 | child: Padding( 32 | padding: const EdgeInsets.all(16.0), 33 | child: Column( 34 | mainAxisSize: MainAxisSize.min, 35 | children: [ 36 | Row( 37 | mainAxisAlignment: MainAxisAlignment.center, 38 | children: [ 39 | const Icon(Icons.info), 40 | Text( 41 | toolInfo, 42 | style: Theme.of(context).textTheme.bodyLarge, 43 | textAlign: TextAlign.center, 44 | ) 45 | ], 46 | ), 47 | const Divider(), 48 | Text( 49 | aboutTitle, 50 | style: Theme.of(context).textTheme.bodyMedium, 51 | textAlign: TextAlign.center, 52 | ), 53 | if (aboutBody != null && aboutBody!.trim().isNotEmpty) 54 | Padding( 55 | padding: const EdgeInsets.only(top: 10.0), 56 | child: Container( 57 | decoration: BoxDecoration( 58 | borderRadius: BorderRadius.circular(12), 59 | color: Theme.of(context).colorScheme.surface, 60 | ), 61 | child: Padding( 62 | padding: const EdgeInsets.symmetric( 63 | horizontal: 16.0, 64 | vertical: 10.0, 65 | ), 66 | child: Column( 67 | crossAxisAlignment: CrossAxisAlignment.stretch, 68 | children: [ 69 | if (aboutBodyTitle != null && 70 | aboutBodyTitle!.trim().isNotEmpty) 71 | Text( 72 | '$aboutBodyTitle\n', 73 | style: Theme.of(context) 74 | .textTheme 75 | .bodyMedium 76 | ?.copyWith( 77 | decoration: TextDecoration.underline, 78 | ), 79 | textAlign: TextAlign.center, 80 | ), 81 | Text( 82 | '$aboutBody', 83 | style: Theme.of(context).textTheme.bodySmall, 84 | textAlign: TextAlign.start, 85 | ), 86 | ], 87 | ), 88 | ), 89 | ), 90 | ), 91 | ], 92 | ), 93 | ), 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/ui/components/view_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/main.dart'; 3 | import 'package:files_tools/state/preferences.dart'; 4 | import 'package:files_tools/ui/components/custom_snack_bar.dart'; 5 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | /// Widget for error icon. 9 | class ErrorIndicator extends StatelessWidget { 10 | /// Defining [ErrorIndicator] constructor. 11 | const ErrorIndicator({Key? key}) : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Column( 16 | mainAxisAlignment: MainAxisAlignment.center, 17 | children: [ 18 | Icon(Icons.error, color: Theme.of(context).colorScheme.error), 19 | ], 20 | ); 21 | } 22 | } 23 | 24 | /// Widget for showing error state. 25 | class ShowError extends StatefulWidget { 26 | /// Defining [ShowError] constructor. 27 | const ShowError({ 28 | Key? key, 29 | required this.errorMessage, 30 | required this.taskMessage, 31 | this.allowBack = false, 32 | required this.errorStackTrace, 33 | }) : super(key: key); 34 | 35 | /// Takes message describing the task for which the error occurred. 36 | final String taskMessage; 37 | 38 | /// Takes message describing the error. 39 | final String errorMessage; 40 | 41 | /// Takes error StackTrace. 42 | final StackTrace? errorStackTrace; 43 | 44 | /// Decides if a back button should be shown or not. 45 | final bool allowBack; 46 | 47 | @override 48 | State createState() => _ShowErrorState(); 49 | } 50 | 51 | class _ShowErrorState extends State { 52 | @override 53 | Widget build(BuildContext context) { 54 | AppLocale appLocale = AppLocale.of(context); 55 | String goBack = appLocale.button_GoBack; 56 | String reportError = appLocale.button_ReportError; 57 | 58 | return Center( 59 | child: Column( 60 | mainAxisAlignment: MainAxisAlignment.center, 61 | children: [ 62 | const ErrorIndicator(), 63 | const SizedBox(height: 16), 64 | Text( 65 | widget.taskMessage, 66 | style: Theme.of(context) 67 | .textTheme 68 | .bodySmall 69 | ?.copyWith(color: Theme.of(context).colorScheme.error), 70 | ), 71 | const SizedBox(height: 16), 72 | Padding( 73 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 74 | child: Container( 75 | // height: 100, 76 | constraints: BoxConstraints( 77 | maxHeight: 100, 78 | maxWidth: MediaQuery.of(context).size.width, 79 | ), 80 | color: Theme.of(context).colorScheme.errorContainer, 81 | child: SingleChildScrollView( 82 | child: Padding( 83 | padding: const EdgeInsets.all(8.0), 84 | child: Text( 85 | widget.errorMessage, 86 | style: Theme.of(context) 87 | .textTheme 88 | .bodySmall 89 | ?.copyWith(color: Theme.of(context).colorScheme.error), 90 | ), 91 | ), 92 | ), 93 | ), 94 | ), 95 | const SizedBox(height: 16), 96 | Row( 97 | mainAxisAlignment: MainAxisAlignment.center, 98 | children: [ 99 | if (widget.allowBack) 100 | FilledButton( 101 | onPressed: () { 102 | Navigator.pop(context); 103 | }, 104 | child: Text(goBack), 105 | ), 106 | TextButton( 107 | onPressed: () async { 108 | if (Preferences.crashlyticsCollectionStatus == false) { 109 | await crashlyticsInstance 110 | .setCrashlyticsCollectionEnabled(true); 111 | } 112 | 113 | FirebaseCrashlytics.instance.recordError( 114 | 'User Reported Error: ${widget.errorMessage}', 115 | widget.errorStackTrace, 116 | reason: 'Task Message: ${widget.taskMessage}', 117 | ); 118 | 119 | if (mounted) { 120 | String? contentText = 'Error reported successfully'; 121 | TextStyle? textStyle = 122 | Theme.of(context).textTheme.bodySmall; 123 | 124 | showCustomSnackBar( 125 | contentText: contentText, 126 | textStyle: textStyle, 127 | context: context, 128 | ); 129 | } 130 | 131 | if (Preferences.crashlyticsCollectionStatus == false) { 132 | await crashlyticsInstance 133 | .setCrashlyticsCollectionEnabled(false); 134 | } 135 | }, 136 | child: Text(reportError), 137 | ), 138 | ], 139 | ), 140 | ], 141 | ), 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/ui/screens/homescreen/homescreen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/ui/components/drawer.dart'; 3 | import 'package:files_tools/ui/screens/homescreen/pages/document_tools_page.dart'; 4 | import 'package:files_tools/ui/screens/homescreen/pages/media_tools_page.dart'; 5 | import 'package:files_tools/utils/utility.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | /// It is the home screen widget. 9 | class HomePage extends StatefulWidget { 10 | /// Defining [HomePage] constructor. 11 | const HomePage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _HomePageState(); 15 | } 16 | 17 | class _HomePageState extends State { 18 | int currentPageIndex = 0; 19 | 20 | @override 21 | void initState() { 22 | Utility.clearTempDirectory(clearCacheCommandFrom: 'HomePage'); 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | AppLocale appLocale = AppLocale.of(context); 29 | String homeScreenTile = appLocale.home_ScreenTitle; 30 | String documentTools = appLocale.documentTools; 31 | String mediaTools = appLocale.mediaTools; 32 | 33 | return DefaultTabController( 34 | length: 2, 35 | child: Scaffold( 36 | drawer: const AppDrawer(), 37 | appBar: AppBar( 38 | title: Text(homeScreenTile), 39 | centerTitle: true, 40 | bottom: TabBar( 41 | tabs: [ 42 | Tab( 43 | text: documentTools, 44 | icon: const Icon(Icons.description), 45 | ), 46 | Tab( 47 | text: mediaTools, 48 | icon: const Icon(Icons.image), 49 | ), 50 | ], 51 | ), 52 | ), 53 | body: const TabBarView( 54 | children: [ 55 | DocumentTools(), 56 | MediaTools(), 57 | ], 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/ui/screens/homescreen/pages/document_tools_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/ui/screens/homescreen/pages/components/pdf_tools_section.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | /// Widget for displaying document tools on home screen. 5 | class DocumentTools extends StatelessWidget { 6 | /// Defining [DocumentTools] constructor. 7 | const DocumentTools({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListView( 12 | children: const [ 13 | SizedBox( 14 | height: 16, 15 | ), 16 | PDFToolsSection(), 17 | SizedBox( 18 | height: 16, 19 | ), 20 | ], 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/ui/screens/homescreen/pages/media_tools_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/ui/screens/homescreen/pages/components/image_tools_section.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | /// Widget for displaying media tools on home screen. 5 | class MediaTools extends StatelessWidget { 6 | /// Defining [MediaTools] constructor. 7 | const MediaTools({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return ListView( 12 | children: const [ 13 | SizedBox( 14 | height: 16, 15 | ), 16 | ImageToolsSection(), 17 | SizedBox( 18 | height: 16, 19 | ), 20 | ], 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/ui/screens/image_tools_screens/compress_image/compress_image_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/image_tools_screens/compress_image/compress_image_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for compressing image. 17 | class CompressImagePage extends StatefulWidget { 18 | /// Defining [CompressImagePage] constructor. 19 | const CompressImagePage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _CompressPDFPageState(); 23 | } 24 | 25 | class _CompressPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'CompressImagePage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String imageSingular = appLocale.image(1); 37 | String imagePlural = appLocale.image(2); 38 | String compressImage = appLocale.tool_CompressFileOrFiles(imageSingular); 39 | String compressImages = appLocale.tool_CompressFileOrFiles(imagePlural); 40 | 41 | return WillPopScope( 42 | onWillPop: () async { 43 | // Removing any snack bar or keyboard 44 | FocusManager.instance.primaryFocus?.unfocus(); 45 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 46 | // Returning true allows the pop to happen, returning false prevents it. 47 | return true; 48 | }, 49 | child: GestureDetector( 50 | onTap: () { 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 53 | }, 54 | child: Scaffold( 55 | appBar: AppBar( 56 | title: Text(compressImage), 57 | centerTitle: true, 58 | ), 59 | body: Consumer( 60 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 61 | final ToolsScreensState watchToolScreenStateProviderValue = 62 | ref.watch(toolsScreensStateProvider); 63 | final List selectedFiles = ref.watch( 64 | toolsScreensStateProvider 65 | .select((ToolsScreensState value) => value.inputFiles), 66 | ); 67 | return ListView( 68 | padding: const EdgeInsets.symmetric(vertical: 16), 69 | children: [ 70 | SelectFilesCard( 71 | fileTypeSingular: imageSingular, 72 | fileTypePlural: imagePlural, 73 | files: watchToolScreenStateProviderValue.inputFiles, 74 | filePickModel: const FilePickModel( 75 | allowedExtensions: [ 76 | '.png', 77 | '.jpeg', 78 | '.jpg', 79 | '.webp', 80 | ], 81 | mimeTypesFilter: [ 82 | 'image/png', 83 | 'image/jpeg', 84 | 'image/webp', 85 | ], 86 | enableMultipleSelection: true, 87 | ), 88 | ), 89 | const SizedBox(height: 16), 90 | ToolActionsCard( 91 | toolActions: [ 92 | ToolActionModel( 93 | actionText: selectedFiles.length > 1 94 | ? compressImages 95 | : compressImage, 96 | actionOnTap: selectedFiles.isNotEmpty 97 | ? () { 98 | // Removing any snack bar or keyboard 99 | FocusManager.instance.primaryFocus?.unfocus(); 100 | ScaffoldMessenger.of(context) 101 | .hideCurrentSnackBar(); 102 | 103 | Navigator.pushNamed( 104 | context, 105 | route.AppRoutes.compressImageToolsPage, 106 | arguments: CompressImageToolsPageArguments( 107 | actionType: ToolAction.compressImages, 108 | files: selectedFiles, 109 | ), 110 | ); 111 | } 112 | : null, 113 | ), 114 | ], 115 | ), 116 | ], 117 | ); 118 | }, 119 | ), 120 | ), 121 | ), 122 | ); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/ui/screens/image_tools_screens/compress_image/compress_image_tool_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/state/tools_actions_state.dart'; 4 | import 'package:files_tools/ui/screens/image_tools_screens/compress_image/actions/compress_image.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Compress image tool actions screen scaffold. 8 | class CompressImageToolsPage extends StatefulWidget { 9 | /// Defining [CompressImageToolsPage] constructor. 10 | const CompressImageToolsPage({Key? key, required this.arguments}) 11 | : super(key: key); 12 | 13 | /// Arguments passed when screen pushed. 14 | final CompressImageToolsPageArguments arguments; 15 | 16 | @override 17 | State createState() => _CompressPDFToolsPageState(); 18 | } 19 | 20 | class _CompressPDFToolsPageState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: () { 30 | FocusManager.instance.primaryFocus?.unfocus(); 31 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 32 | }, 33 | child: Scaffold( 34 | appBar: AppBar( 35 | title: Text( 36 | getAppBarTitleForActionType( 37 | actionType: widget.arguments.actionType, 38 | context: context, 39 | ), 40 | ), 41 | centerTitle: true, 42 | ), 43 | body: CompressImageToolsBody( 44 | actionType: widget.arguments.actionType, 45 | files: widget.arguments.files, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | /// Takes [CompressImageToolsPage] arguments passed when screen pushed. 53 | class CompressImageToolsPageArguments { 54 | /// Defining [CompressImageToolsPageArguments] constructor. 55 | CompressImageToolsPageArguments({ 56 | required this.actionType, 57 | required this.files, 58 | }); 59 | 60 | /// Takes action type. 61 | final ToolAction actionType; 62 | 63 | /// Takes input files. 64 | final List files; 65 | } 66 | 67 | /// [CompressImageToolsPage] screen scaffold body. 68 | class CompressImageToolsBody extends StatelessWidget { 69 | /// Defining [CompressImageToolsBody] constructor. 70 | const CompressImageToolsBody({ 71 | Key? key, 72 | required this.actionType, 73 | required this.files, 74 | }) : super(key: key); 75 | 76 | /// Takes action type. 77 | final ToolAction actionType; 78 | 79 | /// Takes input files. 80 | final List files; 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | if (actionType == ToolAction.compressImages) { 85 | return CompressImage(files: files); 86 | } else { 87 | return Container(); 88 | } 89 | } 90 | } 91 | 92 | /// For getting [CompressImageToolsPage] screen scaffold app bar text. 93 | String getAppBarTitleForActionType({ 94 | required final ToolAction actionType, 95 | required final BuildContext context, 96 | }) { 97 | AppLocale appLocale = AppLocale.of(context); 98 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 99 | String configureCompression = appLocale.tool_Compress_ConfigureCompression; 100 | String title = actionSuccessful; 101 | if (actionType == ToolAction.compressImages) { 102 | title = configureCompression; 103 | } 104 | return title; 105 | } 106 | -------------------------------------------------------------------------------- /lib/ui/screens/image_tools_screens/crop_rotate_flip_images/crop_rotate_flip_images_tool_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/state/tools_actions_state.dart'; 4 | import 'package:files_tools/ui/screens/image_tools_screens/crop_rotate_flip_images/actions/crop_rotate_flip_images.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Modify images tool actions screen scaffold. 8 | class ModifyImagesToolsPage extends StatefulWidget { 9 | /// Defining [ModifyImagesToolsPage] constructor. 10 | const ModifyImagesToolsPage({Key? key, required this.arguments}) 11 | : super(key: key); 12 | 13 | /// Arguments passed when screen pushed. 14 | final ModifyImagesToolsPageArguments arguments; 15 | 16 | @override 17 | State createState() => _ModifyImagesToolsPageState(); 18 | } 19 | 20 | class _ModifyImagesToolsPageState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: () { 30 | FocusManager.instance.primaryFocus?.unfocus(); 31 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 32 | }, 33 | child: Scaffold( 34 | appBar: AppBar( 35 | title: Text( 36 | getAppBarTitleForActionType( 37 | actionType: widget.arguments.actionType, 38 | context: context, 39 | ), 40 | ), 41 | centerTitle: true, 42 | ), 43 | body: ModifyImageToolsBody( 44 | actionType: widget.arguments.actionType, 45 | files: widget.arguments.files, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | /// Takes [ModifyImagesToolsPage] arguments passed when screen pushed. 53 | class ModifyImagesToolsPageArguments { 54 | /// Defining [ModifyImagesToolsPageArguments] constructor. 55 | ModifyImagesToolsPageArguments({ 56 | required this.actionType, 57 | required this.files, 58 | }); 59 | 60 | /// Takes action type. 61 | final ToolAction actionType; 62 | 63 | /// Takes input files. 64 | final List files; 65 | } 66 | 67 | /// [ModifyImagesToolsPage] screen scaffold body. 68 | class ModifyImageToolsBody extends StatelessWidget { 69 | /// Defining [ModifyImageToolsBody] constructor. 70 | const ModifyImageToolsBody({ 71 | Key? key, 72 | required this.actionType, 73 | required this.files, 74 | }) : super(key: key); 75 | 76 | /// Takes action type. 77 | final ToolAction actionType; 78 | 79 | /// Takes input files. 80 | final List files; 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | if (actionType == ToolAction.cropRotateFlipImages) { 85 | return CropRotateFlipImages(files: files); 86 | } else { 87 | return Container(); 88 | } 89 | } 90 | } 91 | 92 | /// For getting [ModifyImagesToolsPage] screen scaffold app bar text. 93 | String getAppBarTitleForActionType({ 94 | required final ToolAction actionType, 95 | required final BuildContext context, 96 | }) { 97 | AppLocale appLocale = AppLocale.of(context); 98 | String imageSingular = appLocale.image(1); 99 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 100 | String cropRotateFlipImage = 101 | appLocale.tool_Modify_CropRotateFlipFileOrFiles(imageSingular); 102 | String title = actionSuccessful; 103 | if (actionType == ToolAction.cropRotateFlipImages) { 104 | title = cropRotateFlipImage; 105 | } 106 | return title; 107 | } 108 | -------------------------------------------------------------------------------- /lib/ui/screens/image_tools_screens/pdf_to_image/pdf_to_image_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/convert_pdf/convert_pdf_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for converting PDF to image. 17 | class PdfToImagePage extends StatefulWidget { 18 | /// Defining [PdfToImagePage] constructor. 19 | const PdfToImagePage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _PdfToImagePageState(); 23 | } 24 | 25 | class _PdfToImagePageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'PDFToImagePage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(1); 38 | String imageSingular = appLocale.image(1); 39 | String pdfToImage = appLocale.tool_FileOrFiles1ToFileOrFiles2( 40 | pdfSingular, 41 | imageSingular, 42 | ); 43 | String convertPdfToImage = appLocale.tool_ConvertFileOrFiles1ToFileOrFiles2( 44 | pdfSingular, 45 | imageSingular, 46 | ); 47 | 48 | return WillPopScope( 49 | onWillPop: () async { 50 | // Removing any snack bar or keyboard 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 53 | // Returning true allows the pop to happen, returning false prevents it. 54 | return true; 55 | }, 56 | child: GestureDetector( 57 | onTap: () { 58 | FocusManager.instance.primaryFocus?.unfocus(); 59 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 60 | }, 61 | child: Scaffold( 62 | appBar: AppBar( 63 | title: Text(pdfToImage), 64 | centerTitle: true, 65 | ), 66 | body: Consumer( 67 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 68 | final ToolsScreensState watchToolScreenStateProviderValue = 69 | ref.watch(toolsScreensStateProvider); 70 | final List selectedFiles = ref.watch( 71 | toolsScreensStateProvider 72 | .select((ToolsScreensState value) => value.inputFiles), 73 | ); 74 | return ListView( 75 | padding: const EdgeInsets.symmetric(vertical: 16), 76 | children: [ 77 | SelectFilesCard( 78 | fileTypeSingular: pdfSingular, 79 | fileTypePlural: pdfPlural, 80 | files: watchToolScreenStateProviderValue.inputFiles, 81 | filePickModel: const FilePickModel( 82 | allowedExtensions: [ 83 | '.pdf', 84 | ], 85 | mimeTypesFilter: [ 86 | 'application/pdf', 87 | ], 88 | discardInvalidPdfFiles: true, 89 | discardProtectedPdfFiles: true, 90 | ), 91 | ), 92 | const SizedBox(height: 16), 93 | ToolActionsCard( 94 | toolActions: [ 95 | ToolActionModel( 96 | actionText: convertPdfToImage, 97 | actionOnTap: selectedFiles.length == 1 98 | ? () { 99 | // Removing any snack bar or keyboard 100 | FocusManager.instance.primaryFocus?.unfocus(); 101 | ScaffoldMessenger.of(context) 102 | .hideCurrentSnackBar(); 103 | 104 | Navigator.pushNamed( 105 | context, 106 | route.AppRoutes.convertPDFToolsPage, 107 | arguments: ConvertPDFToolsPageArguments( 108 | actionType: ToolAction.convertPdfToImage, 109 | file: selectedFiles[0], 110 | ), 111 | ); 112 | } 113 | : null, 114 | ), 115 | ], 116 | ), 117 | ], 118 | ); 119 | }, 120 | ), 121 | ), 122 | ), 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/compress_pdf/compress_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/compress_pdf/compress_pdf_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for compressing PDF. 17 | class CompressPDFPage extends StatefulWidget { 18 | /// Defining [CompressPDFPage] constructor. 19 | const CompressPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _CompressPDFPageState(); 23 | } 24 | 25 | class _CompressPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'CompressPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String compressPdf = appLocale.tool_CompressFileOrFiles(pdfSingular); 39 | String compressPdfs = appLocale.tool_CompressFileOrFiles(pdfPlural); 40 | 41 | return WillPopScope( 42 | onWillPop: () async { 43 | // Removing any snack bar or keyboard 44 | FocusManager.instance.primaryFocus?.unfocus(); 45 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 46 | // Returning true allows the pop to happen, returning false prevents it. 47 | return true; 48 | }, 49 | child: GestureDetector( 50 | onTap: () { 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 53 | }, 54 | child: Scaffold( 55 | appBar: AppBar( 56 | title: Text(compressPdf), 57 | centerTitle: true, 58 | ), 59 | body: Consumer( 60 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 61 | final ToolsScreensState watchToolScreenStateProviderValue = 62 | ref.watch(toolsScreensStateProvider); 63 | final List selectedFiles = ref.watch( 64 | toolsScreensStateProvider 65 | .select((ToolsScreensState value) => value.inputFiles), 66 | ); 67 | return ListView( 68 | padding: const EdgeInsets.symmetric(vertical: 16), 69 | children: [ 70 | SelectFilesCard( 71 | fileTypeSingular: pdfSingular, 72 | fileTypePlural: pdfPlural, 73 | files: watchToolScreenStateProviderValue.inputFiles, 74 | filePickModel: const FilePickModel( 75 | allowedExtensions: [ 76 | '.pdf', 77 | ], 78 | mimeTypesFilter: [ 79 | 'application/pdf', 80 | ], 81 | discardInvalidPdfFiles: true, 82 | discardProtectedPdfFiles: true, 83 | ), 84 | ), 85 | const SizedBox(height: 16), 86 | ToolActionsCard( 87 | toolActions: [ 88 | ToolActionModel( 89 | actionText: selectedFiles.length > 1 90 | ? compressPdfs 91 | : compressPdf, 92 | actionOnTap: selectedFiles.length == 1 93 | ? () { 94 | // Removing any snack bar or keyboard 95 | FocusManager.instance.primaryFocus?.unfocus(); 96 | ScaffoldMessenger.of(context) 97 | .hideCurrentSnackBar(); 98 | 99 | Navigator.pushNamed( 100 | context, 101 | route.AppRoutes.compressPDFToolsPage, 102 | arguments: CompressPDFToolsPageArguments( 103 | actionType: ToolAction.compressPdf, 104 | file: selectedFiles[0], 105 | ), 106 | ); 107 | } 108 | : null, 109 | ), 110 | ], 111 | ), 112 | ], 113 | ); 114 | }, 115 | ), 116 | ), 117 | ), 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/compress_pdf/compress_pdf_tool_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:files_tools/l10n/generated/app_locale.dart'; 4 | import 'package:files_tools/models/file_model.dart'; 5 | import 'package:files_tools/state/tools_actions_state.dart'; 6 | import 'package:files_tools/ui/components/loading.dart'; 7 | import 'package:files_tools/ui/components/view_error.dart'; 8 | import 'package:files_tools/ui/screens/pdf_tools_screens/compress_pdf/actions/compress_pdf.dart'; 9 | import 'package:files_tools/utils/utility.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | /// Compress PDF tool actions screen scaffold. 13 | class CompressPDFToolsPage extends StatefulWidget { 14 | /// Defining [CompressPDFToolsPage] constructor. 15 | const CompressPDFToolsPage({Key? key, required this.arguments}) 16 | : super(key: key); 17 | 18 | /// Arguments passed when screen pushed. 19 | final CompressPDFToolsPageArguments arguments; 20 | 21 | @override 22 | State createState() => _CompressPDFToolsPageState(); 23 | } 24 | 25 | class _CompressPDFToolsPageState extends State { 26 | late Future initPageCount; 27 | late int pdfPageCount; 28 | 29 | Future initPdfPageCount() async { 30 | pdfPageCount = 31 | await Utility.getPdfPageCount(pdfPath: widget.arguments.file.fileUri); 32 | return true; 33 | } 34 | 35 | @override 36 | void initState() { 37 | initPageCount = initPdfPageCount(); 38 | super.initState(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | AppLocale appLocale = AppLocale.of(context); 44 | 45 | String pdfSingular = appLocale.pdf(1); 46 | String loadingText = appLocale.tool_Action_LoadingFileOrFiles(pdfSingular); 47 | 48 | return GestureDetector( 49 | onTap: () { 50 | FocusManager.instance.primaryFocus?.unfocus(); 51 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 52 | }, 53 | child: Scaffold( 54 | appBar: AppBar( 55 | title: Text( 56 | getAppBarTitleForActionType( 57 | actionType: widget.arguments.actionType, 58 | context: context, 59 | ), 60 | ), 61 | centerTitle: true, 62 | ), 63 | body: FutureBuilder( 64 | future: initPageCount, // async work 65 | builder: (BuildContext context, AsyncSnapshot snapshot) { 66 | switch (snapshot.connectionState) { 67 | case ConnectionState.waiting: 68 | return Loading( 69 | loadingText: loadingText, 70 | ); 71 | case ConnectionState.none: 72 | return Loading( 73 | loadingText: loadingText, 74 | ); 75 | case ConnectionState.active: 76 | return Loading( 77 | loadingText: loadingText, 78 | ); 79 | case ConnectionState.done: 80 | if (snapshot.hasError) { 81 | log(snapshot.error.toString()); 82 | return ShowError( 83 | taskMessage: 'Sorry, failed to process the pdf.', 84 | errorMessage: snapshot.error.toString(), 85 | errorStackTrace: snapshot.stackTrace, 86 | allowBack: true, 87 | ); 88 | } else { 89 | return CompressPDFToolsBody( 90 | actionType: widget.arguments.actionType, 91 | file: widget.arguments.file, 92 | pdfPageCount: pdfPageCount, 93 | ); 94 | } 95 | } 96 | }, 97 | ), 98 | ), 99 | ); 100 | } 101 | } 102 | 103 | /// Takes [CompressPDFToolsPage] arguments passed when screen pushed. 104 | class CompressPDFToolsPageArguments { 105 | /// Defining [CompressPDFToolsPageArguments] constructor. 106 | CompressPDFToolsPageArguments({required this.actionType, required this.file}); 107 | 108 | /// Takes action type. 109 | final ToolAction actionType; 110 | 111 | /// Takes input file. 112 | final InputFileModel file; 113 | } 114 | 115 | /// [CompressPDFToolsPage] screen scaffold body. 116 | class CompressPDFToolsBody extends StatelessWidget { 117 | /// Defining [CompressPDFToolsBody] constructor. 118 | const CompressPDFToolsBody({ 119 | Key? key, 120 | required this.actionType, 121 | required this.file, 122 | required this.pdfPageCount, 123 | }) : super(key: key); 124 | 125 | /// Takes action type. 126 | final ToolAction actionType; 127 | 128 | /// Takes input file. 129 | final InputFileModel file; 130 | 131 | /// Takes PDF file total page number. 132 | final int pdfPageCount; 133 | 134 | @override 135 | Widget build(BuildContext context) { 136 | if (actionType == ToolAction.compressPdf) { 137 | return CompressPDF(pdfPageCount: pdfPageCount, file: file); 138 | } else { 139 | return Container(); 140 | } 141 | } 142 | } 143 | 144 | /// For getting [CompressPDFToolsPage] screen scaffold app bar text. 145 | String getAppBarTitleForActionType({ 146 | required final ToolAction actionType, 147 | required final BuildContext context, 148 | }) { 149 | AppLocale appLocale = AppLocale.of(context); 150 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 151 | String configureCompression = appLocale.tool_Compress_ConfigureCompression; 152 | String title = actionSuccessful; 153 | if (actionType == ToolAction.compressPdf) { 154 | title = configureCompression; 155 | } 156 | return title; 157 | } 158 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/convert_pdf/convert_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/convert_pdf/convert_pdf_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for converting PDF. 17 | class ConvertPDFPage extends StatefulWidget { 18 | /// Defining [ConvertPDFPage] constructor. 19 | const ConvertPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _ConvertPDFPageState(); 23 | } 24 | 25 | class _ConvertPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'ConvertPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String imageSingular = appLocale.image(1); 39 | String convertPdfFormat = 40 | appLocale.tool_ConvertFileOrFilesFormat(pdfSingular); 41 | String convertPdfToImage = appLocale.tool_ConvertFileOrFiles1ToFileOrFiles2( 42 | pdfSingular, 43 | imageSingular, 44 | ); 45 | 46 | return WillPopScope( 47 | onWillPop: () async { 48 | // Removing any snack bar or keyboard 49 | FocusManager.instance.primaryFocus?.unfocus(); 50 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 51 | // Returning true allows the pop to happen, returning false prevents it. 52 | return true; 53 | }, 54 | child: GestureDetector( 55 | onTap: () { 56 | FocusManager.instance.primaryFocus?.unfocus(); 57 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 58 | }, 59 | child: Scaffold( 60 | appBar: AppBar( 61 | title: Text(convertPdfFormat), 62 | centerTitle: true, 63 | ), 64 | body: Consumer( 65 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 66 | final ToolsScreensState watchToolScreenStateProviderValue = 67 | ref.watch(toolsScreensStateProvider); 68 | final List selectedFiles = ref.watch( 69 | toolsScreensStateProvider 70 | .select((ToolsScreensState value) => value.inputFiles), 71 | ); 72 | return ListView( 73 | padding: const EdgeInsets.symmetric(vertical: 16), 74 | children: [ 75 | SelectFilesCard( 76 | fileTypeSingular: pdfSingular, 77 | fileTypePlural: pdfPlural, 78 | files: watchToolScreenStateProviderValue.inputFiles, 79 | filePickModel: const FilePickModel( 80 | allowedExtensions: [ 81 | '.pdf', 82 | ], 83 | mimeTypesFilter: [ 84 | 'application/pdf', 85 | ], 86 | discardInvalidPdfFiles: true, 87 | discardProtectedPdfFiles: true, 88 | ), 89 | ), 90 | const SizedBox(height: 16), 91 | ToolActionsCard( 92 | toolActions: [ 93 | ToolActionModel( 94 | actionText: convertPdfToImage, 95 | actionOnTap: selectedFiles.length == 1 96 | ? () { 97 | // Removing any snack bar or keyboard 98 | FocusManager.instance.primaryFocus?.unfocus(); 99 | ScaffoldMessenger.of(context) 100 | .hideCurrentSnackBar(); 101 | 102 | Navigator.pushNamed( 103 | context, 104 | route.AppRoutes.convertPDFToolsPage, 105 | arguments: ConvertPDFToolsPageArguments( 106 | actionType: ToolAction.convertPdfToImage, 107 | file: selectedFiles[0], 108 | ), 109 | ); 110 | } 111 | : null, 112 | ), 113 | ], 114 | ), 115 | ], 116 | ); 117 | }, 118 | ), 119 | ), 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/decrypt_pdf/actions/decrypt_pdf.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/route/app_routes.dart' as route; 4 | import 'package:files_tools/state/providers.dart'; 5 | import 'package:files_tools/state/tools_actions_state.dart'; 6 | import 'package:files_tools/ui/components/tools_about_card.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 10 | 11 | /// Action screen for decrypting PDF. 12 | class DecryptPDF extends StatelessWidget { 13 | /// Defining [DecryptPDF] constructor. 14 | const DecryptPDF({Key? key, required this.file}) : super(key: key); 15 | 16 | /// Takes input file. 17 | final InputFileModel file; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | AppLocale appLocale = AppLocale.of(context); 22 | 23 | String pdfSingular = appLocale.pdf(1); 24 | String aboutDecryptTitle = appLocale.tool_Decrypt_InfoTitle(pdfSingular); 25 | String aboutDecryptPDFBody = 26 | appLocale.tool_DecryptPDF_InfoBody(pdfSingular); 27 | 28 | return ListView( 29 | padding: const EdgeInsets.symmetric(vertical: 16), 30 | children: [ 31 | DecryptPDFActionCard(file: file), 32 | const SizedBox(height: 16), 33 | AboutActionCard( 34 | aboutTitle: aboutDecryptTitle, 35 | aboutBody: aboutDecryptPDFBody, 36 | ), 37 | ], 38 | ); 39 | } 40 | } 41 | 42 | /// Card for decryption configuration. 43 | class DecryptPDFActionCard extends StatefulWidget { 44 | /// Defining [DecryptPDFActionCard] constructor. 45 | const DecryptPDFActionCard({Key? key, required this.file}) : super(key: key); 46 | 47 | /// Takes input file. 48 | final InputFileModel file; 49 | 50 | @override 51 | State createState() => _DecryptPDFActionCardState(); 52 | } 53 | 54 | class _DecryptPDFActionCardState extends State { 55 | final GlobalKey _formKey = GlobalKey(); 56 | 57 | TextEditingController passwordController = TextEditingController(text: ''); 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | AppLocale appLocale = AppLocale.of(context); 62 | 63 | String enterOwnerOrUserPw = 64 | appLocale.textField_LabelText_EnterOwnerOrUserPw; 65 | String enterOwnerOrUserPwHlpText = 66 | appLocale.textField_HelperText_LeaveFldEmptyIfNoOwnerPwSet; 67 | String process = appLocale.button_Process; 68 | 69 | return Card( 70 | clipBehavior: Clip.antiAlias, 71 | margin: const EdgeInsets.symmetric(horizontal: 16.0), 72 | child: Padding( 73 | padding: const EdgeInsets.all(16.0), 74 | child: Column( 75 | mainAxisSize: MainAxisSize.min, 76 | children: [ 77 | const Icon(Icons.looks_3), 78 | const Divider(), 79 | Form( 80 | key: _formKey, 81 | child: Column( 82 | crossAxisAlignment: CrossAxisAlignment.start, 83 | children: [ 84 | TextFormField( 85 | controller: passwordController, 86 | decoration: InputDecoration( 87 | filled: true, 88 | labelText: enterOwnerOrUserPw, 89 | isDense: true, 90 | helperText: enterOwnerOrUserPwHlpText, 91 | // enabledBorder: const UnderlineInputBorder(), 92 | ), 93 | inputFormatters: [ 94 | FilteringTextInputFormatter.deny(RegExp(r'\s')), 95 | ], 96 | autovalidateMode: AutovalidateMode.onUserInteraction, 97 | // The validator receives the text that the user 98 | // has entered. 99 | validator: (String? value) { 100 | return null; 101 | }, 102 | ), 103 | ], 104 | ), 105 | ), 106 | const SizedBox(height: 10), 107 | Column( 108 | children: [ 109 | const Divider(), 110 | Consumer( 111 | builder: 112 | (BuildContext context, WidgetRef ref, Widget? child) { 113 | final ToolsActionsState 114 | watchToolsActionsStateProviderValue = 115 | ref.watch(toolsActionsStateProvider); 116 | return FilledButton.icon( 117 | onPressed: () async { 118 | if (_formKey.currentState!.validate()) { 119 | watchToolsActionsStateProviderValue 120 | .mangeDecryptPdfFileAction( 121 | sourceFile: widget.file, 122 | password: passwordController.value.text, 123 | ); 124 | Navigator.pushNamed( 125 | context, 126 | route.AppRoutes.resultPage, 127 | ); 128 | } 129 | }, 130 | icon: const Icon(Icons.check), 131 | label: Text(process), 132 | ); 133 | }, 134 | ), 135 | ], 136 | ), 137 | ], 138 | ), 139 | ), 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/decrypt_pdf/decrypt_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/decrypt_pdf/decrypt_pdf_tools_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for decrypting PDF. 17 | class DecryptPDFPage extends StatefulWidget { 18 | /// Defining [DecryptPDFPage] constructor. 19 | const DecryptPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _DecryptPDFPageState(); 23 | } 24 | 25 | class _DecryptPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'DecryptPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String decryptPdf = appLocale.tool_DecryptFileOrFiles(pdfSingular); 39 | 40 | return WillPopScope( 41 | onWillPop: () async { 42 | // Removing any snack bar or keyboard 43 | FocusManager.instance.primaryFocus?.unfocus(); 44 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 45 | // Returning true allows the pop to happen, returning false prevents it. 46 | return true; 47 | }, 48 | child: GestureDetector( 49 | onTap: () { 50 | FocusManager.instance.primaryFocus?.unfocus(); 51 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 52 | }, 53 | child: Scaffold( 54 | appBar: AppBar( 55 | title: Text(decryptPdf), 56 | centerTitle: true, 57 | ), 58 | body: Consumer( 59 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 60 | final ToolsScreensState watchToolScreenStateProviderValue = 61 | ref.watch(toolsScreensStateProvider); 62 | final List selectedFiles = ref.watch( 63 | toolsScreensStateProvider 64 | .select((ToolsScreensState value) => value.inputFiles), 65 | ); 66 | return ListView( 67 | padding: const EdgeInsets.symmetric(vertical: 16), 68 | children: [ 69 | SelectFilesCard( 70 | fileTypeSingular: pdfSingular, 71 | fileTypePlural: pdfPlural, 72 | files: watchToolScreenStateProviderValue.inputFiles, 73 | filePickModel: const FilePickModel( 74 | allowedExtensions: [ 75 | '.pdf', 76 | ], 77 | mimeTypesFilter: [ 78 | 'application/pdf', 79 | ], 80 | discardInvalidPdfFiles: true, 81 | ), 82 | ), 83 | const SizedBox(height: 16), 84 | ToolActionsCard( 85 | toolActions: [ 86 | ToolActionModel( 87 | actionText: decryptPdf, 88 | actionOnTap: selectedFiles.length == 1 89 | ? () { 90 | // Removing any snack bar or keyboard 91 | FocusManager.instance.primaryFocus?.unfocus(); 92 | ScaffoldMessenger.of(context) 93 | .hideCurrentSnackBar(); 94 | 95 | Navigator.pushNamed( 96 | context, 97 | route.AppRoutes.decryptPDFToolsPage, 98 | arguments: DecryptPDFToolsPageArguments( 99 | actionType: ToolAction.decryptPdf, 100 | file: selectedFiles[0], 101 | ), 102 | ); 103 | } 104 | : null, 105 | ), 106 | ], 107 | ), 108 | ], 109 | ); 110 | }, 111 | ), 112 | ), 113 | ), 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/decrypt_pdf/decrypt_pdf_tools_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/state/tools_actions_state.dart'; 4 | import 'package:files_tools/ui/screens/pdf_tools_screens/decrypt_pdf/actions/decrypt_pdf.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Decrypt PDF tool actions screen scaffold. 8 | class DecryptPDFToolsPage extends StatefulWidget { 9 | /// Defining [DecryptPDFToolsPage] constructor. 10 | const DecryptPDFToolsPage({Key? key, required this.arguments}) 11 | : super(key: key); 12 | 13 | /// Arguments passed when screen pushed. 14 | final DecryptPDFToolsPageArguments arguments; 15 | 16 | @override 17 | State createState() => _DecryptPDFToolsPageState(); 18 | } 19 | 20 | class _DecryptPDFToolsPageState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: () { 30 | FocusManager.instance.primaryFocus?.unfocus(); 31 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 32 | }, 33 | child: Scaffold( 34 | appBar: AppBar( 35 | title: Text( 36 | getAppBarTitleForActionType( 37 | actionType: widget.arguments.actionType, 38 | context: context, 39 | ), 40 | ), 41 | centerTitle: true, 42 | ), 43 | body: DecryptPDFToolsBody( 44 | actionType: widget.arguments.actionType, 45 | file: widget.arguments.file, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | /// Takes [DecryptPDFToolsPage] arguments passed when screen pushed. 53 | class DecryptPDFToolsPageArguments { 54 | /// Defining [DecryptPDFToolsPageArguments] constructor. 55 | DecryptPDFToolsPageArguments({required this.actionType, required this.file}); 56 | 57 | /// Takes action type. 58 | final ToolAction actionType; 59 | 60 | /// Takes input file. 61 | final InputFileModel file; 62 | } 63 | 64 | /// [DecryptPDFToolsPage] screen scaffold body. 65 | class DecryptPDFToolsBody extends StatelessWidget { 66 | /// Defining [DecryptPDFToolsBody] constructor. 67 | const DecryptPDFToolsBody({ 68 | Key? key, 69 | required this.actionType, 70 | required this.file, 71 | }) : super(key: key); 72 | 73 | /// Takes action type. 74 | final ToolAction actionType; 75 | 76 | /// Takes input file. 77 | final InputFileModel file; 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | if (actionType == ToolAction.decryptPdf) { 82 | return DecryptPDF(file: file); 83 | } else { 84 | return Container(); 85 | } 86 | } 87 | } 88 | 89 | /// For getting [DecryptPDFToolsPage] screen scaffold app bar text. 90 | String getAppBarTitleForActionType({ 91 | required final ToolAction actionType, 92 | required final BuildContext context, 93 | }) { 94 | AppLocale appLocale = AppLocale.of(context); 95 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 96 | String configureDecryption = appLocale.tool_Decrypt_ConfigureDecryption; 97 | String title = actionSuccessful; 98 | if (actionType == ToolAction.decryptPdf) { 99 | title = configureDecryption; 100 | } 101 | return title; 102 | } 103 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/encrypt_pdf/encrypt_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/encrypt_pdf/encrypt_pdf_tools_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for encrypting PDF. 17 | class EncryptPDFPage extends StatefulWidget { 18 | /// Defining [EncryptPDFPage] constructor. 19 | const EncryptPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _EncryptPDFPageState(); 23 | } 24 | 25 | class _EncryptPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'EncryptPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String encryptPdf = appLocale.tool_EncryptFileOrFiles(pdfSingular); 39 | 40 | return WillPopScope( 41 | onWillPop: () async { 42 | // Removing any snack bar or keyboard 43 | FocusManager.instance.primaryFocus?.unfocus(); 44 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 45 | // Returning true allows the pop to happen, returning false prevents it. 46 | return true; 47 | }, 48 | child: GestureDetector( 49 | onTap: () { 50 | FocusManager.instance.primaryFocus?.unfocus(); 51 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 52 | }, 53 | child: Scaffold( 54 | appBar: AppBar( 55 | title: Text(encryptPdf), 56 | centerTitle: true, 57 | ), 58 | body: Consumer( 59 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 60 | final ToolsScreensState watchToolScreenStateProviderValue = 61 | ref.watch(toolsScreensStateProvider); 62 | final List selectedFiles = ref.watch( 63 | toolsScreensStateProvider 64 | .select((ToolsScreensState value) => value.inputFiles), 65 | ); 66 | return ListView( 67 | padding: const EdgeInsets.symmetric(vertical: 16), 68 | children: [ 69 | SelectFilesCard( 70 | fileTypeSingular: pdfSingular, 71 | fileTypePlural: pdfPlural, 72 | files: watchToolScreenStateProviderValue.inputFiles, 73 | filePickModel: const FilePickModel( 74 | allowedExtensions: [ 75 | '.pdf', 76 | ], 77 | mimeTypesFilter: [ 78 | 'application/pdf', 79 | ], 80 | discardInvalidPdfFiles: true, 81 | discardProtectedPdfFiles: true, 82 | ), 83 | ), 84 | const SizedBox(height: 16), 85 | ToolActionsCard( 86 | toolActions: [ 87 | ToolActionModel( 88 | actionText: encryptPdf, 89 | actionOnTap: selectedFiles.length == 1 90 | ? () { 91 | // Removing any snack bar or keyboard 92 | FocusManager.instance.primaryFocus?.unfocus(); 93 | ScaffoldMessenger.of(context) 94 | .hideCurrentSnackBar(); 95 | 96 | Navigator.pushNamed( 97 | context, 98 | route.AppRoutes.encryptPDFToolsPage, 99 | arguments: EncryptPDFToolsPageArguments( 100 | actionType: ToolAction.encryptPdf, 101 | file: selectedFiles[0], 102 | ), 103 | ); 104 | } 105 | : null, 106 | ), 107 | ], 108 | ), 109 | ], 110 | ); 111 | }, 112 | ), 113 | ), 114 | ), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/encrypt_pdf/encrypt_pdf_tools_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/state/tools_actions_state.dart'; 4 | import 'package:files_tools/ui/screens/pdf_tools_screens/encrypt_pdf/actions/encrypt_pdf.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Encrypt PDF tool actions screen scaffold. 8 | class EncryptPDFToolsPage extends StatefulWidget { 9 | /// Defining [EncryptPDFToolsPage] constructor. 10 | const EncryptPDFToolsPage({Key? key, required this.arguments}) 11 | : super(key: key); 12 | 13 | /// Arguments passed when screen pushed. 14 | final EncryptPDFToolsPageArguments arguments; 15 | 16 | @override 17 | State createState() => _EncryptPDFToolsPageState(); 18 | } 19 | 20 | class _EncryptPDFToolsPageState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: () { 30 | FocusManager.instance.primaryFocus?.unfocus(); 31 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 32 | }, 33 | child: Scaffold( 34 | appBar: AppBar( 35 | title: Text( 36 | getAppBarTitleForActionType( 37 | actionType: widget.arguments.actionType, 38 | context: context, 39 | ), 40 | ), 41 | centerTitle: true, 42 | ), 43 | body: EncryptPDFToolsBody( 44 | actionType: widget.arguments.actionType, 45 | file: widget.arguments.file, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | /// Takes [EncryptPDFToolsPage] arguments passed when screen pushed. 53 | class EncryptPDFToolsPageArguments { 54 | /// Defining [EncryptPDFToolsPageArguments] constructor. 55 | EncryptPDFToolsPageArguments({required this.actionType, required this.file}); 56 | 57 | /// Takes action type. 58 | final ToolAction actionType; 59 | 60 | /// Takes input file. 61 | final InputFileModel file; 62 | } 63 | 64 | /// [EncryptPDFToolsPage] screen scaffold body. 65 | class EncryptPDFToolsBody extends StatelessWidget { 66 | /// Defining [EncryptPDFToolsBody] constructor. 67 | const EncryptPDFToolsBody({ 68 | Key? key, 69 | required this.actionType, 70 | required this.file, 71 | }) : super(key: key); 72 | 73 | /// Takes action type. 74 | final ToolAction actionType; 75 | 76 | /// Takes input file. 77 | final InputFileModel file; 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | if (actionType == ToolAction.encryptPdf) { 82 | return EncryptPDF(file: file); 83 | } else { 84 | return Container(); 85 | } 86 | } 87 | } 88 | 89 | /// For getting [EncryptPDFToolsPage] screen scaffold app bar text. 90 | String getAppBarTitleForActionType({ 91 | required final ToolAction actionType, 92 | required final BuildContext context, 93 | }) { 94 | AppLocale appLocale = AppLocale.of(context); 95 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 96 | String configureEncryption = appLocale.tool_Encrypt_ConfigureEncryption; 97 | String title = actionSuccessful; 98 | if (actionType == ToolAction.encryptPdf) { 99 | title = configureEncryption; 100 | } 101 | return title; 102 | } 103 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/image_to_pdf/image_to_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/image_to_pdf/image_to_pdf_tools_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for converting images to PDF. 17 | class ImageToPDFPage extends StatefulWidget { 18 | /// Defining [ImageToPDFPage] constructor. 19 | const ImageToPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _ImageToPDFPageState(); 23 | } 24 | 25 | class _ImageToPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'ImageToPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String imageSingular = appLocale.image(1); 38 | String imagePlural = appLocale.image(2); 39 | String imageToPdf = appLocale.tool_FileOrFiles1ToFileOrFiles2( 40 | imageSingular, 41 | pdfSingular, 42 | ); 43 | String convertImageToPdf = appLocale.tool_ConvertFileOrFiles1ToFileOrFiles2( 44 | imageSingular, 45 | pdfSingular, 46 | ); 47 | 48 | return WillPopScope( 49 | onWillPop: () async { 50 | // Removing any snack bar or keyboard 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 53 | // Returning true allows the pop to happen, returning false prevents it. 54 | return true; 55 | }, 56 | child: GestureDetector( 57 | onTap: () { 58 | FocusManager.instance.primaryFocus?.unfocus(); 59 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 60 | }, 61 | child: Scaffold( 62 | appBar: AppBar( 63 | title: Text(imageToPdf), 64 | centerTitle: true, 65 | ), 66 | body: Consumer( 67 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 68 | final ToolsScreensState watchToolScreenStateProviderValue = 69 | ref.watch(toolsScreensStateProvider); 70 | // final List selectedImages = ref.watch( 71 | // toolScreenStateProvider 72 | // .select((value) => value.selectedImages)); 73 | final List selectedFiles = ref.watch( 74 | toolsScreensStateProvider 75 | .select((ToolsScreensState value) => value.inputFiles), 76 | ); 77 | return ListView( 78 | padding: const EdgeInsets.symmetric(vertical: 16), 79 | children: [ 80 | SelectFilesCard( 81 | fileTypeSingular: imageSingular, 82 | fileTypePlural: imagePlural, 83 | files: watchToolScreenStateProviderValue.inputFiles, 84 | filePickModel: const FilePickModel( 85 | allowedExtensions: [ 86 | '.JPEG', 87 | '.JPG', 88 | '.JP2', 89 | '.GIF', 90 | '.PNG', 91 | '.BMP', 92 | '.WMF', 93 | '.TIFF', 94 | '.CCITT', 95 | '.JBIG2', 96 | ], 97 | mimeTypesFilter: [ 98 | // "image/*", 99 | 'image/png', 100 | 'image/gif', 101 | 'image/jpeg', 102 | ], 103 | enableMultipleSelection: true, 104 | ), 105 | ), 106 | const SizedBox(height: 16), 107 | ToolActionsCard( 108 | toolActions: [ 109 | ToolActionModel( 110 | actionText: convertImageToPdf, 111 | actionOnTap: selectedFiles.isNotEmpty 112 | ? () { 113 | // Removing any snack bar or keyboard 114 | FocusManager.instance.primaryFocus?.unfocus(); 115 | ScaffoldMessenger.of(context) 116 | .hideCurrentSnackBar(); 117 | 118 | Navigator.pushNamed( 119 | context, 120 | route.AppRoutes.imageToPDFToolsPage, 121 | arguments: ImageToPDFToolsPageArguments( 122 | actionType: ToolAction.imageToPdf, 123 | files: selectedFiles, 124 | ), 125 | ); 126 | } 127 | : null, 128 | ), 129 | ], 130 | ), 131 | ], 132 | ); 133 | }, 134 | ), 135 | ), 136 | ), 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/image_to_pdf/image_to_pdf_tools_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/state/tools_actions_state.dart'; 4 | import 'package:files_tools/ui/screens/pdf_tools_screens/image_to_pdf/actions/image_to_pdf.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Converting image to PDF tool actions screen scaffold. 8 | class ImageToPDFToolsPage extends StatefulWidget { 9 | /// Defining [ImageToPDFToolsPage] constructor. 10 | const ImageToPDFToolsPage({Key? key, required this.arguments}) 11 | : super(key: key); 12 | 13 | /// Arguments passed when screen pushed. 14 | final ImageToPDFToolsPageArguments arguments; 15 | 16 | @override 17 | State createState() => _ImageToPDFToolsPageState(); 18 | } 19 | 20 | class _ImageToPDFToolsPageState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: () { 30 | FocusManager.instance.primaryFocus?.unfocus(); 31 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 32 | }, 33 | child: Scaffold( 34 | appBar: AppBar( 35 | title: Text( 36 | getAppBarTitleForActionType( 37 | actionType: widget.arguments.actionType, 38 | context: context, 39 | ), 40 | ), 41 | centerTitle: true, 42 | ), 43 | body: ImageToPDFToolsBody( 44 | actionType: widget.arguments.actionType, 45 | files: widget.arguments.files, 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | /// Takes [ImageToPDFToolsPage] arguments passed when screen pushed. 53 | class ImageToPDFToolsPageArguments { 54 | /// Defining [ImageToPDFToolsPageArguments] constructor. 55 | ImageToPDFToolsPageArguments({required this.actionType, required this.files}); 56 | 57 | /// Takes action type. 58 | final ToolAction actionType; 59 | 60 | /// Takes list of model of PDF pages. 61 | final List files; 62 | } 63 | 64 | /// [ImageToPDFToolsPage] screen scaffold body. 65 | class ImageToPDFToolsBody extends StatelessWidget { 66 | /// Defining [ImageToPDFToolsBody] constructor. 67 | const ImageToPDFToolsBody({ 68 | Key? key, 69 | required this.actionType, 70 | required this.files, 71 | }) : super(key: key); 72 | 73 | /// Takes action type. 74 | final ToolAction actionType; 75 | 76 | /// Takes list of model of PDF pages. 77 | final List files; 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | if (actionType == ToolAction.imageToPdf) { 82 | return ImageToPDF(files: files); 83 | } else { 84 | return Container(); 85 | } 86 | } 87 | } 88 | 89 | /// For getting [ImageToPDFToolsPage] screen scaffold app bar text. 90 | String getAppBarTitleForActionType({ 91 | required final ToolAction actionType, 92 | required final BuildContext context, 93 | }) { 94 | AppLocale appLocale = AppLocale.of(context); 95 | String pdfSingular = appLocale.pdf(1); 96 | String imagePlural = appLocale.image(2); 97 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 98 | String prepareImagesForPdf = appLocale 99 | .tool_Action_PrepareFileOrFiles1ForFileOrFiles2(imagePlural, pdfSingular); 100 | String title = actionSuccessful; 101 | if (actionType == ToolAction.imageToPdf) { 102 | title = prepareImagesForPdf; 103 | } 104 | return title; 105 | } 106 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/merge_pdfs_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/utils/utility.dart'; 12 | import 'package:flutter/material.dart'; 13 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 14 | 15 | /// Tool screen for merging PDFs. 16 | class MergePDFsPage extends StatefulWidget { 17 | /// Defining [MergePDFsPage] constructor. 18 | const MergePDFsPage({Key? key}) : super(key: key); 19 | 20 | @override 21 | State createState() => _MergePDFsPageState(); 22 | } 23 | 24 | class _MergePDFsPageState extends State { 25 | @override 26 | void initState() { 27 | Utility.clearTempDirectory(clearCacheCommandFrom: 'MergePDFsPage'); 28 | super.initState(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | AppLocale appLocale = AppLocale.of(context); 34 | 35 | String pdfSingular = appLocale.pdf(1); 36 | String pdfPlural = appLocale.pdf(2); 37 | String mergePdf = appLocale.tool_MergeFileOrFiles(pdfSingular); 38 | String mergePdfs = appLocale.tool_MergeFileOrFiles(pdfPlural); 39 | 40 | return WillPopScope( 41 | onWillPop: () async { 42 | // Removing any snack bar or keyboard 43 | FocusManager.instance.primaryFocus?.unfocus(); 44 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 45 | // Returning true allows the pop to happen, returning false prevents it. 46 | return true; 47 | }, 48 | child: GestureDetector( 49 | onTap: () { 50 | FocusManager.instance.primaryFocus?.unfocus(); 51 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 52 | }, 53 | child: Scaffold( 54 | appBar: AppBar( 55 | title: Text(mergePdf), 56 | centerTitle: true, 57 | ), 58 | body: Consumer( 59 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 60 | final ToolsScreensState watchToolScreenStateProviderValue = 61 | ref.watch(toolsScreensStateProvider); 62 | final List selectedFiles = ref.watch( 63 | toolsScreensStateProvider 64 | .select((ToolsScreensState value) => value.inputFiles), 65 | ); 66 | final ToolsActionsState watchToolsActionsStateProviderValue = 67 | ref.watch(toolsActionsStateProvider); 68 | return ListView( 69 | padding: const EdgeInsets.symmetric(vertical: 16), 70 | children: [ 71 | SelectFilesCard( 72 | fileTypeSingular: pdfSingular, 73 | fileTypePlural: pdfPlural, 74 | files: watchToolScreenStateProviderValue.inputFiles, 75 | filePickModel: const FilePickModel( 76 | allowedExtensions: [ 77 | '.pdf', 78 | ], 79 | mimeTypesFilter: [ 80 | 'application/pdf', 81 | ], 82 | enableMultipleSelection: true, 83 | discardInvalidPdfFiles: true, 84 | discardProtectedPdfFiles: true, 85 | multipleFilePickRequired: true, 86 | ), 87 | ), 88 | const SizedBox(height: 16), 89 | ToolActionsCard( 90 | toolActions: [ 91 | ToolActionModel( 92 | actionText: 93 | selectedFiles.length > 1 ? mergePdfs : mergePdf, 94 | actionOnTap: selectedFiles.length >= 2 95 | ? () { 96 | // Removing any snack bar or keyboard 97 | FocusManager.instance.primaryFocus?.unfocus(); 98 | ScaffoldMessenger.of(context) 99 | .hideCurrentSnackBar(); 100 | 101 | watchToolsActionsStateProviderValue 102 | .mangeMergePdfFileAction( 103 | sourceFiles: selectedFiles, 104 | ); 105 | 106 | Navigator.pushNamed( 107 | context, 108 | route.AppRoutes.resultPage, 109 | ); 110 | } 111 | : null, 112 | ), 113 | ], 114 | ), 115 | ], 116 | ); 117 | }, 118 | ), 119 | ), 120 | ), 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/modify_pdf/modify_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/modify_pdf/modify_pdf_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for modifying PDF. 17 | class ModifyPDFPage extends StatefulWidget { 18 | /// Defining [ModifyPDFPage] constructor. 19 | const ModifyPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _ModifyPDFPageState(); 23 | } 24 | 25 | class _ModifyPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'ModifyPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String modifyPdf = appLocale.tool_ModifyFileOrFiles(pdfSingular); 39 | String rotateDeleteReorderPdf = 40 | appLocale.tool_Modify_RotateDeleteReorderFileOrFilesPages(pdfSingular); 41 | 42 | return WillPopScope( 43 | onWillPop: () async { 44 | // Removing any snack bar or keyboard 45 | FocusManager.instance.primaryFocus?.unfocus(); 46 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 47 | // Returning true allows the pop to happen, returning false prevents it. 48 | return true; 49 | }, 50 | child: GestureDetector( 51 | onTap: () { 52 | FocusManager.instance.primaryFocus?.unfocus(); 53 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 54 | }, 55 | child: Scaffold( 56 | appBar: AppBar( 57 | title: Text(modifyPdf), 58 | centerTitle: true, 59 | ), 60 | body: Consumer( 61 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 62 | final ToolsScreensState watchToolScreenStateProviderValue = 63 | ref.watch(toolsScreensStateProvider); 64 | final List selectedFiles = ref.watch( 65 | toolsScreensStateProvider 66 | .select((ToolsScreensState value) => value.inputFiles), 67 | ); 68 | return ListView( 69 | padding: const EdgeInsets.symmetric(vertical: 16), 70 | children: [ 71 | SelectFilesCard( 72 | fileTypeSingular: pdfSingular, 73 | fileTypePlural: pdfPlural, 74 | files: watchToolScreenStateProviderValue.inputFiles, 75 | filePickModel: const FilePickModel( 76 | allowedExtensions: [ 77 | '.pdf', 78 | ], 79 | mimeTypesFilter: [ 80 | 'application/pdf', 81 | ], 82 | discardInvalidPdfFiles: true, 83 | discardProtectedPdfFiles: true, 84 | ), 85 | ), 86 | const SizedBox(height: 16), 87 | ToolActionsCard( 88 | toolActions: [ 89 | ToolActionModel( 90 | actionText: rotateDeleteReorderPdf, 91 | actionOnTap: selectedFiles.length == 1 92 | ? () { 93 | // Removing any snack bar or keyboard 94 | FocusManager.instance.primaryFocus?.unfocus(); 95 | ScaffoldMessenger.of(context) 96 | .hideCurrentSnackBar(); 97 | 98 | Navigator.pushNamed( 99 | context, 100 | route.AppRoutes.modifyPDFToolsPage, 101 | arguments: ModifyPDFToolsPageArguments( 102 | actionType: ToolAction.modifyPdf, 103 | file: selectedFiles[0], 104 | ), 105 | ); 106 | } 107 | : null, 108 | ), 109 | ], 110 | ), 111 | ], 112 | ); 113 | }, 114 | ), 115 | ), 116 | ), 117 | ); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/watermark_pdf/watermark_pdf_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/models/file_model.dart'; 3 | import 'package:files_tools/models/file_pick_save_model.dart'; 4 | import 'package:files_tools/models/tool_actions_model.dart'; 5 | import 'package:files_tools/route/app_routes.dart' as route; 6 | import 'package:files_tools/state/providers.dart'; 7 | import 'package:files_tools/state/tools_actions_state.dart'; 8 | import 'package:files_tools/state/tools_screens_state.dart'; 9 | import 'package:files_tools/ui/components/select_file_section.dart'; 10 | import 'package:files_tools/ui/components/tool_actions_section.dart'; 11 | import 'package:files_tools/ui/screens/pdf_tools_screens/watermark_pdf/watermark_pdf_tool_action_screen.dart'; 12 | import 'package:files_tools/utils/utility.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 15 | 16 | /// Tool screen for watermarking PDF. 17 | class WatermarkPDFPage extends StatefulWidget { 18 | /// Defining [WatermarkPDFPage] constructor. 19 | const WatermarkPDFPage({Key? key}) : super(key: key); 20 | 21 | @override 22 | State createState() => _WatermarkPDFPageState(); 23 | } 24 | 25 | class _WatermarkPDFPageState extends State { 26 | @override 27 | void initState() { 28 | Utility.clearTempDirectory(clearCacheCommandFrom: 'WatermarkPDFPage'); 29 | super.initState(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | AppLocale appLocale = AppLocale.of(context); 35 | 36 | String pdfSingular = appLocale.pdf(1); 37 | String pdfPlural = appLocale.pdf(2); 38 | String watermarkPdf = appLocale.tool_WatermarkFileOrFiles(pdfSingular); 39 | String watermarkPdfs = appLocale.tool_WatermarkFileOrFiles(pdfPlural); 40 | 41 | return WillPopScope( 42 | onWillPop: () async { 43 | // Removing any snack bar or keyboard 44 | FocusManager.instance.primaryFocus?.unfocus(); 45 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 46 | // Returning true allows the pop to happen, returning false prevents it. 47 | return true; 48 | }, 49 | child: GestureDetector( 50 | onTap: () { 51 | FocusManager.instance.primaryFocus?.unfocus(); 52 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 53 | }, 54 | child: Scaffold( 55 | appBar: AppBar( 56 | title: Text(watermarkPdf), 57 | centerTitle: true, 58 | ), 59 | body: Consumer( 60 | builder: (BuildContext context, WidgetRef ref, Widget? child) { 61 | final ToolsScreensState watchToolScreenStateProviderValue = 62 | ref.watch(toolsScreensStateProvider); 63 | final List selectedFiles = ref.watch( 64 | toolsScreensStateProvider 65 | .select((ToolsScreensState value) => value.inputFiles), 66 | ); 67 | return ListView( 68 | padding: const EdgeInsets.symmetric(vertical: 16), 69 | children: [ 70 | SelectFilesCard( 71 | fileTypeSingular: pdfSingular, 72 | fileTypePlural: pdfPlural, 73 | files: watchToolScreenStateProviderValue.inputFiles, 74 | filePickModel: const FilePickModel( 75 | allowedExtensions: [ 76 | '.pdf', 77 | ], 78 | mimeTypesFilter: [ 79 | 'application/pdf', 80 | ], 81 | discardInvalidPdfFiles: true, 82 | discardProtectedPdfFiles: true, 83 | ), 84 | ), 85 | const SizedBox(height: 16), 86 | ToolActionsCard( 87 | toolActions: [ 88 | ToolActionModel( 89 | actionText: selectedFiles.length > 1 90 | ? watermarkPdfs 91 | : watermarkPdf, 92 | actionOnTap: selectedFiles.length == 1 93 | ? () { 94 | // Removing any snack bar or keyboard 95 | FocusManager.instance.primaryFocus?.unfocus(); 96 | ScaffoldMessenger.of(context) 97 | .hideCurrentSnackBar(); 98 | 99 | Navigator.pushNamed( 100 | context, 101 | route.AppRoutes.watermarkPDFToolsPage, 102 | arguments: WatermarkPDFToolsPageArguments( 103 | actionType: ToolAction.watermarkPdf, 104 | file: selectedFiles[0], 105 | ), 106 | ); 107 | } 108 | : null, 109 | ), 110 | ], 111 | ), 112 | ], 113 | ); 114 | }, 115 | ), 116 | ), 117 | ), 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/ui/screens/pdf_tools_screens/watermark_pdf/watermark_pdf_tool_action_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | 3 | import 'package:files_tools/l10n/generated/app_locale.dart'; 4 | import 'package:files_tools/models/file_model.dart'; 5 | import 'package:files_tools/state/tools_actions_state.dart'; 6 | import 'package:files_tools/ui/components/loading.dart'; 7 | import 'package:files_tools/ui/components/view_error.dart'; 8 | import 'package:files_tools/ui/screens/pdf_tools_screens/watermark_pdf/actions/watermark_pdf.dart'; 9 | import 'package:files_tools/utils/utility.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | /// Watermark PDF tool actions screen scaffold. 13 | class WatermarkPDFToolsPage extends StatefulWidget { 14 | /// Defining [WatermarkPDFToolsPage] constructor. 15 | const WatermarkPDFToolsPage({Key? key, required this.arguments}) 16 | : super(key: key); 17 | 18 | /// Arguments passed when screen pushed. 19 | final WatermarkPDFToolsPageArguments arguments; 20 | 21 | @override 22 | State createState() => _WatermarkPDFToolsPageState(); 23 | } 24 | 25 | class _WatermarkPDFToolsPageState extends State { 26 | late Future initPageCount; 27 | late int pdfPageCount; 28 | 29 | Future initPdfPageCount() async { 30 | pdfPageCount = 31 | await Utility.getPdfPageCount(pdfPath: widget.arguments.file.fileUri); 32 | return true; 33 | } 34 | 35 | @override 36 | void initState() { 37 | initPageCount = initPdfPageCount(); 38 | super.initState(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | AppLocale appLocale = AppLocale.of(context); 44 | 45 | String pdfSingular = appLocale.pdf(1); 46 | String loadingText = appLocale.tool_Action_LoadingFileOrFiles(pdfSingular); 47 | 48 | return GestureDetector( 49 | onTap: () { 50 | FocusManager.instance.primaryFocus?.unfocus(); 51 | ScaffoldMessenger.of(context).hideCurrentSnackBar(); 52 | }, 53 | child: Scaffold( 54 | appBar: AppBar( 55 | title: Text( 56 | getAppBarTitleForActionType( 57 | actionType: widget.arguments.actionType, 58 | context: context, 59 | ), 60 | ), 61 | centerTitle: true, 62 | ), 63 | body: FutureBuilder( 64 | future: initPageCount, // async work 65 | builder: (BuildContext context, AsyncSnapshot snapshot) { 66 | switch (snapshot.connectionState) { 67 | case ConnectionState.waiting: 68 | return Loading( 69 | loadingText: loadingText, 70 | ); 71 | case ConnectionState.none: 72 | return Loading( 73 | loadingText: loadingText, 74 | ); 75 | case ConnectionState.active: 76 | return Loading( 77 | loadingText: loadingText, 78 | ); 79 | case ConnectionState.done: 80 | if (snapshot.hasError) { 81 | log(snapshot.error.toString()); 82 | return ShowError( 83 | taskMessage: 'Sorry, failed to process the pdf.', 84 | errorMessage: snapshot.error.toString(), 85 | errorStackTrace: snapshot.stackTrace, 86 | allowBack: true, 87 | ); 88 | } else { 89 | return WatermarkPDFToolsBody( 90 | actionType: widget.arguments.actionType, 91 | file: widget.arguments.file, 92 | pdfPageCount: pdfPageCount, 93 | ); 94 | } 95 | } 96 | }, 97 | ), 98 | ), 99 | ); 100 | } 101 | } 102 | 103 | /// Takes [WatermarkPDFToolsPage] arguments passed when screen pushed. 104 | class WatermarkPDFToolsPageArguments { 105 | /// Defining [WatermarkPDFToolsPageArguments] constructor. 106 | WatermarkPDFToolsPageArguments({ 107 | required this.actionType, 108 | required this.file, 109 | }); 110 | 111 | /// Takes action type. 112 | final ToolAction actionType; 113 | 114 | /// Takes input file. 115 | final InputFileModel file; 116 | } 117 | 118 | /// [WatermarkPDFToolsPage] screen scaffold body. 119 | class WatermarkPDFToolsBody extends StatelessWidget { 120 | /// Defining [WatermarkPDFToolsPage] constructor. 121 | const WatermarkPDFToolsBody({ 122 | Key? key, 123 | required this.actionType, 124 | required this.file, 125 | required this.pdfPageCount, 126 | }) : super(key: key); 127 | 128 | /// Takes action type. 129 | final ToolAction actionType; 130 | 131 | /// Takes input file. 132 | final InputFileModel file; 133 | 134 | /// Takes PDF file total page number. 135 | final int pdfPageCount; 136 | 137 | @override 138 | Widget build(BuildContext context) { 139 | if (actionType == ToolAction.watermarkPdf) { 140 | return WatermarkPDF(pdfPageCount: pdfPageCount, file: file); 141 | } else { 142 | return Container(); 143 | } 144 | } 145 | } 146 | 147 | /// For getting [WatermarkPDFToolsPage] screen scaffold app bar text. 148 | String getAppBarTitleForActionType({ 149 | required final ToolAction actionType, 150 | required final BuildContext context, 151 | }) { 152 | AppLocale appLocale = AppLocale.of(context); 153 | String actionSuccessful = appLocale.tool_Action_ProcessingScreen_Successful; 154 | String configureWatermark = appLocale.tool_Watermark_ConfigureWatermark; 155 | String title = actionSuccessful; 156 | if (actionType == ToolAction.watermarkPdf) { 157 | title = configureWatermark; 158 | } 159 | return title; 160 | } 161 | -------------------------------------------------------------------------------- /lib/ui/screens/settings_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:files_tools/l10n/generated/app_locale.dart'; 2 | import 'package:files_tools/ui/components/crashlytics_analytics_switch.dart'; 3 | import 'package:files_tools/ui/components/dynamic_theme_switch_tile.dart'; 4 | import 'package:files_tools/ui/components/reset_app_theme_settings.dart'; 5 | import 'package:files_tools/ui/components/theme_chooser_widget.dart'; 6 | import 'package:files_tools/ui/components/theme_mode_switcher.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | /// It is the settings screen widget. 10 | class SettingsPage extends StatelessWidget { 11 | /// Defining [SettingsPage] constructor. 12 | const SettingsPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | AppLocale appLocale = AppLocale.of(context); 17 | 18 | String settings = appLocale.settings_ScreenTitle; 19 | String theming = appLocale.settings_Theming_Title; 20 | String usageAndDiagnostics = appLocale.settings_UsageAndDiagnostics_Title; 21 | String usageAndDiagnosticsNote = 22 | appLocale.settings_UsageAndDiagnostics_About; 23 | 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text(settings), 27 | centerTitle: true, 28 | ), 29 | body: ListView( 30 | padding: const EdgeInsets.symmetric(vertical: 16), 31 | children: [ 32 | Padding( 33 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 34 | child: Row( 35 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 36 | children: [ 37 | Text( 38 | theming, 39 | style: Theme.of(context).textTheme.bodyMedium, 40 | ), 41 | const ResetAppThemeSettings(), 42 | ], 43 | ), 44 | ), 45 | const ThemeChooserWidget(), 46 | const DynamicThemeSwitchTile(), 47 | const ThemeModeSwitcher(), 48 | const SizedBox(height: 8), 49 | Padding( 50 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 51 | child: Row( 52 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 53 | children: [ 54 | Text( 55 | usageAndDiagnostics, 56 | style: Theme.of(context).textTheme.bodyMedium, 57 | ), 58 | ], 59 | ), 60 | ), 61 | const SizedBox(height: 8), 62 | const CrashlyticsSwitchTile(), 63 | const AnalyticsSwitchTile(), 64 | Padding( 65 | padding: const EdgeInsets.all(16.0), 66 | child: Text( 67 | usageAndDiagnosticsNote, 68 | style: Theme.of(context).textTheme.bodySmall, 69 | ), 70 | ) 71 | ], 72 | ), 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/ui/theme/app_theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:dynamic_color/dynamic_color.dart'; 2 | import 'package:files_tools/ui/theme/color_schemes.g.dart'; 3 | import 'package:files_tools/ui/theme/custom_color.g.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | /// For creating app [ThemeData] by providing any color schemes. 7 | class AppThemeData { 8 | static final Color _lightFocusColor = Colors.black.withOpacity(0.12); 9 | static final Color _darkFocusColor = Colors.white.withOpacity(0.12); 10 | 11 | /// For creating app light ThemeData by providing any light color scheme. 12 | static ThemeData lightThemeData(final ColorScheme? customLightColorScheme) => 13 | themeData( 14 | customLightColorScheme, 15 | defaultLightColorScheme, 16 | _lightFocusColor, 17 | lightCustomColors, 18 | ); 19 | 20 | /// For creating app dark ThemeData by providing any dark color scheme. 21 | static ThemeData darkThemeData(final ColorScheme? customDarkColorScheme) => 22 | themeData( 23 | customDarkColorScheme, 24 | defaultDarkColorScheme, 25 | _darkFocusColor, 26 | darkCustomColors, 27 | ); 28 | 29 | /// For creating ThemeData properties of [lightThemeData] and [darkThemeData]. 30 | static ThemeData themeData( 31 | final ColorScheme? customColorScheme, 32 | final ColorScheme colorScheme, 33 | final Color focusColor, 34 | CustomColors customColors, 35 | ) { 36 | ColorScheme newColorScheme = colorScheme; 37 | 38 | if (customColorScheme != null) { 39 | newColorScheme = customColorScheme.harmonized(); 40 | customColors = customColors.harmonized(newColorScheme); 41 | } 42 | 43 | return ThemeData( 44 | useMaterial3: true, 45 | colorScheme: newColorScheme, 46 | extensions: >[ 47 | customColors, 48 | ], 49 | fontFamily: 'LexendDeca', 50 | focusColor: focusColor, 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/ui/theme/color_schemes.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const defaultLightColorScheme = ColorScheme( 4 | brightness: Brightness.light, 5 | primary: Color(0xFFA93054), 6 | onPrimary: Color(0xFFFFFFFF), 7 | primaryContainer: Color(0xFFFFD9DF), 8 | onPrimaryContainer: Color(0xFF3F0016), 9 | secondary: Color(0xFF9A4059), 10 | onSecondary: Color(0xFFFFFFFF), 11 | secondaryContainer: Color(0xFFFFD9DF), 12 | onSecondaryContainer: Color(0xFF3F0018), 13 | tertiary: Color(0xFF8A5100), 14 | onTertiary: Color(0xFFFFFFFF), 15 | tertiaryContainer: Color(0xFFFFDCBD), 16 | onTertiaryContainer: Color(0xFF2C1600), 17 | error: Color(0xFFBA1A1A), 18 | errorContainer: Color(0xFFFFDAD6), 19 | onError: Color(0xFFFFFFFF), 20 | onErrorContainer: Color(0xFF410002), 21 | background: Color(0xFFFFFBFF), 22 | onBackground: Color(0xFF201A1B), 23 | surface: Color(0xFFFFFBFF), 24 | onSurface: Color(0xFF201A1B), 25 | surfaceVariant: Color(0xFFF3DDE0), 26 | onSurfaceVariant: Color(0xFF524345), 27 | outline: Color(0xFF847375), 28 | onInverseSurface: Color(0xFFFAEEEE), 29 | inverseSurface: Color(0xFF362F30), 30 | inversePrimary: Color(0xFFFFB1C0), 31 | shadow: Color(0xFF000000), 32 | surfaceTint: Color(0xFFA93054), 33 | outlineVariant: Color(0xFFD6C2C4), 34 | scrim: Color(0xFF000000), 35 | ); 36 | 37 | const defaultDarkColorScheme = ColorScheme( 38 | brightness: Brightness.dark, 39 | primary: Color(0xFFFFB1C0), 40 | onPrimary: Color(0xFF660028), 41 | primaryContainer: Color(0xFF89163D), 42 | onPrimaryContainer: Color(0xFFFFD9DF), 43 | secondary: Color(0xFFFFB1C1), 44 | onSecondary: Color(0xFF5F112C), 45 | secondaryContainer: Color(0xFF7C2942), 46 | onSecondaryContainer: Color(0xFFFFD9DF), 47 | tertiary: Color(0xFFFFB86D), 48 | onTertiary: Color(0xFF492900), 49 | tertiaryContainer: Color(0xFF683C00), 50 | onTertiaryContainer: Color(0xFFFFDCBD), 51 | error: Color(0xFFFFB4AB), 52 | errorContainer: Color(0xFF93000A), 53 | onError: Color(0xFF690005), 54 | onErrorContainer: Color(0xFFFFDAD6), 55 | background: Color(0xFF201A1B), 56 | onBackground: Color(0xFFECE0E0), 57 | surface: Color(0xFF201A1B), 58 | onSurface: Color(0xFFECE0E0), 59 | surfaceVariant: Color(0xFF524345), 60 | onSurfaceVariant: Color(0xFFD6C2C4), 61 | outline: Color(0xFF9F8C8F), 62 | onInverseSurface: Color(0xFF201A1B), 63 | inverseSurface: Color(0xFFECE0E0), 64 | inversePrimary: Color(0xFFA93054), 65 | shadow: Color(0xFF000000), 66 | surfaceTint: Color(0xFFFFB1C0), 67 | outlineVariant: Color(0xFF524345), 68 | scrim: Color(0xFF000000), 69 | ); 70 | -------------------------------------------------------------------------------- /lib/ui/theme/custom_color.g.dart: -------------------------------------------------------------------------------- 1 | import 'package:dynamic_color/dynamic_color.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | const turquoise = Color(0xFF0081A7); 5 | const pink = Color(0xFFFFC8DD); 6 | 7 | CustomColors lightCustomColors = const CustomColors( 8 | sourceTurquoise: Color(0xFF0081A7), 9 | turquoise: Color(0xFF006685), 10 | onTurquoise: Color(0xFFFFFFFF), 11 | turquoiseContainer: Color(0xFFBFE9FF), 12 | onTurquoiseContainer: Color(0xFF001F2A), 13 | sourcePink: Color(0xFFFFC8DD), 14 | pink: Color(0xFF96416B), 15 | onPink: Color(0xFFFFFFFF), 16 | pinkContainer: Color(0xFFFFD8E6), 17 | onPinkContainer: Color(0xFF3D0024), 18 | ); 19 | 20 | CustomColors darkCustomColors = const CustomColors( 21 | sourceTurquoise: Color(0xFF0081A7), 22 | turquoise: Color(0xFF6CD2FF), 23 | onTurquoise: Color(0xFF003547), 24 | turquoiseContainer: Color(0xFF004D65), 25 | onTurquoiseContainer: Color(0xFFBFE9FF), 26 | sourcePink: Color(0xFFFFC8DD), 27 | pink: Color(0xFFFFAFD1), 28 | onPink: Color(0xFF5C113B), 29 | pinkContainer: Color(0xFF792952), 30 | onPinkContainer: Color(0xFFFFD8E6), 31 | ); 32 | 33 | /// Defines a set of custom colors, each comprised of 4 complementary tones. 34 | /// 35 | /// See also: 36 | /// * 37 | @immutable 38 | class CustomColors extends ThemeExtension { 39 | const CustomColors({ 40 | required this.sourceTurquoise, 41 | required this.turquoise, 42 | required this.onTurquoise, 43 | required this.turquoiseContainer, 44 | required this.onTurquoiseContainer, 45 | required this.sourcePink, 46 | required this.pink, 47 | required this.onPink, 48 | required this.pinkContainer, 49 | required this.onPinkContainer, 50 | }); 51 | 52 | final Color? sourceTurquoise; 53 | final Color? turquoise; 54 | final Color? onTurquoise; 55 | final Color? turquoiseContainer; 56 | final Color? onTurquoiseContainer; 57 | final Color? sourcePink; 58 | final Color? pink; 59 | final Color? onPink; 60 | final Color? pinkContainer; 61 | final Color? onPinkContainer; 62 | 63 | @override 64 | CustomColors copyWith({ 65 | Color? sourceTurquoise, 66 | Color? turquoise, 67 | Color? onTurquoise, 68 | Color? turquoiseContainer, 69 | Color? onTurquoiseContainer, 70 | Color? sourcePink, 71 | Color? pink, 72 | Color? onPink, 73 | Color? pinkContainer, 74 | Color? onPinkContainer, 75 | }) { 76 | return CustomColors( 77 | sourceTurquoise: sourceTurquoise ?? this.sourceTurquoise, 78 | turquoise: turquoise ?? this.turquoise, 79 | onTurquoise: onTurquoise ?? this.onTurquoise, 80 | turquoiseContainer: turquoiseContainer ?? this.turquoiseContainer, 81 | onTurquoiseContainer: onTurquoiseContainer ?? this.onTurquoiseContainer, 82 | sourcePink: sourcePink ?? this.sourcePink, 83 | pink: pink ?? this.pink, 84 | onPink: onPink ?? this.onPink, 85 | pinkContainer: pinkContainer ?? this.pinkContainer, 86 | onPinkContainer: onPinkContainer ?? this.onPinkContainer, 87 | ); 88 | } 89 | 90 | @override 91 | CustomColors lerp(ThemeExtension? other, double t) { 92 | if (other is! CustomColors) { 93 | return this; 94 | } 95 | return CustomColors( 96 | sourceTurquoise: Color.lerp(sourceTurquoise, other.sourceTurquoise, t), 97 | turquoise: Color.lerp(turquoise, other.turquoise, t), 98 | onTurquoise: Color.lerp(onTurquoise, other.onTurquoise, t), 99 | turquoiseContainer: 100 | Color.lerp(turquoiseContainer, other.turquoiseContainer, t), 101 | onTurquoiseContainer: 102 | Color.lerp(onTurquoiseContainer, other.onTurquoiseContainer, t), 103 | sourcePink: Color.lerp(sourcePink, other.sourcePink, t), 104 | pink: Color.lerp(pink, other.pink, t), 105 | onPink: Color.lerp(onPink, other.onPink, t), 106 | pinkContainer: Color.lerp(pinkContainer, other.pinkContainer, t), 107 | onPinkContainer: Color.lerp(onPinkContainer, other.onPinkContainer, t), 108 | ); 109 | } 110 | 111 | /// Returns an instance of [CustomColors] in which the following custom 112 | /// colors are harmonized with [dynamic]'s [ColorScheme.primary]. 113 | /// * [CustomColors.sourceTurquoise] 114 | /// * [CustomColors.turquoise] 115 | /// * [CustomColors.onTurquoise] 116 | /// * [CustomColors.turquoiseContainer] 117 | /// * [CustomColors.onTurquoiseContainer] 118 | /// 119 | /// See also: 120 | /// * 121 | CustomColors harmonized(ColorScheme dynamic) { 122 | return copyWith( 123 | sourceTurquoise: sourceTurquoise!.harmonizeWith(dynamic.primary), 124 | turquoise: turquoise!.harmonizeWith(dynamic.primary), 125 | onTurquoise: onTurquoise!.harmonizeWith(dynamic.primary), 126 | turquoiseContainer: turquoiseContainer!.harmonizeWith(dynamic.primary), 127 | onTurquoiseContainer: 128 | onTurquoiseContainer!.harmonizeWith(dynamic.primary), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/utils/decimal_text_input_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/services.dart'; 4 | 5 | /// Text input formatter to help restrict TextField text input to a decimal 6 | /// input of certain decimal range. 7 | class DecimalTextInputFormatter extends TextInputFormatter { 8 | /// Defining [DecimalTextInputFormatter] constructor. 9 | DecimalTextInputFormatter({required this.decimalRange}) 10 | : assert(decimalRange > 0); 11 | 12 | /// Takes decimal range. 13 | final int decimalRange; 14 | 15 | @override 16 | TextEditingValue formatEditUpdate( 17 | TextEditingValue oldValue, 18 | TextEditingValue newValue, 19 | ) { 20 | // Holds the new value text caret value. 21 | TextSelection newSelection = newValue.selection; 22 | // Holds the new value text. 23 | String truncated = newValue.text; 24 | 25 | if (truncated.contains('.') && 26 | (truncated.substring(truncated.indexOf('.') + 1).length > 27 | decimalRange)) { 28 | truncated = oldValue.text; 29 | newSelection = oldValue.selection; 30 | } else if (truncated == '.') { 31 | truncated = '0.'; 32 | 33 | newSelection = newValue.selection.copyWith( 34 | baseOffset: math.min(truncated.length, truncated.length + 1), 35 | extentOffset: math.min(truncated.length, truncated.length + 1), 36 | ); 37 | } else if (truncated.contains('.')) { 38 | String tempValue = truncated.substring(truncated.indexOf('.') + 1); 39 | if (tempValue.contains('.')) { 40 | truncated = oldValue.text; 41 | newSelection = oldValue.selection; 42 | } 43 | if (oldValue.text.length < truncated.length && 44 | truncated.indexOf('.') == 0) { 45 | truncated = '0$truncated'; 46 | newSelection = newValue.selection.copyWith( 47 | baseOffset: math.min(truncated.length, truncated.length + 1), 48 | extentOffset: math.min(truncated.length, truncated.length + 1), 49 | ); 50 | } 51 | } 52 | if (truncated.contains(' ') || truncated.contains(',')) { 53 | truncated = oldValue.text; 54 | newSelection = oldValue.selection; 55 | } 56 | 57 | return TextEditingValue( 58 | text: truncated, 59 | selection: newSelection, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/utils/pick_save.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:pick_or_save/pick_or_save.dart'; 3 | 4 | /// PickSave is a utility class for all the picking and saving. 5 | class PickSave { 6 | /// For saving files. 7 | static Future?> saveFiles({ 8 | required final FileSaverParams params, 9 | }) async { 10 | // Holds save result. 11 | List? saveResult; 12 | 13 | try { 14 | // Saving the files and storing result paths in saveResult. 15 | saveResult = await PickOrSave().fileSaver( 16 | params: params, 17 | ); 18 | } on PlatformException { 19 | rethrow; 20 | } catch (e) { 21 | rethrow; 22 | } 23 | 24 | return saveResult; 25 | } 26 | 27 | /// For file picking. 28 | static Future?> pickFile({ 29 | required final FilePickerParams params, 30 | }) async { 31 | // Holds picking result. 32 | List? pickResult; 33 | 34 | try { 35 | // Picking the files and storing result paths in pickResult. 36 | pickResult = await PickOrSave().filePicker(params: params); 37 | } on PlatformException { 38 | rethrow; 39 | } catch (e) { 40 | rethrow; 41 | } 42 | 43 | return pickResult; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 in the flutter_test package. 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:files_tools/main.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(const DynamicColorApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | --------------------------------------------------------------------------------