├── .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 | [](https://wakatime.com/badge/user/83f3b15d-49de-4c01-b8de-bbc132f11be1/project/3876495a-70a6-486e-b999-b6854d83bdc9)
4 |
5 |
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 |
--------------------------------------------------------------------------------