├── .gitattributes
├── .gitignore
├── .metadata
├── AppImageBuilder.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── FUNDING.yml
├── LICENSE
├── PRIVACY_POLICY.md
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── printnotes
│ │ │ │ └── MainActivity.kt
│ │ ├── play_store_512.png
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ └── ic_launcher.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ ├── ic_launcher_background.png
│ │ │ ├── ic_launcher_foreground.png
│ │ │ └── ic_launcher_monochrome.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── app_icon.png
├── app_icon_no-bg.png
└── icons
│ ├── ftp-icon.png
│ ├── nextcloud-icon.png
│ └── rsync-icon.png
├── devtools_options.yaml
├── images
├── AllThemes.png
├── AllThemes2-smaller.png
├── Desktop
│ ├── DesktopEditor.png
│ ├── DesktopGridView.png
│ ├── DesktopListView.png
│ └── DesktopTreeView.png
├── Phone
│ ├── IphoneEditor.png
│ ├── IphoneListView.png
│ ├── PhoneAdvancedSearch.png
│ ├── PhoneEditing.png
│ ├── PhoneEditor.png
│ ├── PhoneGridView.png
│ ├── PhoneListView.png
│ ├── PhoneSettings.png
│ ├── PhoneTreeView.png
│ ├── iPadEditing.png
│ ├── iPadEditor.png
│ ├── iPadGridView.png
│ ├── iPhoneEditing.png
│ └── iPhoneGridView.png
├── Wiki
│ ├── Basics
│ │ ├── home-fab.png
│ │ ├── image-files.png
│ │ ├── pdf-viewer.png
│ │ ├── web-image-text.png
│ │ └── web-images.png
│ ├── Search
│ │ ├── search-example.png
│ │ └── search-view.png
│ ├── Settings
│ │ ├── settings-layour.png
│ │ ├── settings-page.png
│ │ └── settings-sort.png
│ └── Setup
│ │ ├── init-allow-storage-access.png
│ │ ├── init-directory.png
│ │ ├── init-select-folder.png
│ │ └── init-theme.png
└── play_store_feature_graphic.png
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── AppIcon-20@2x.png
│ │ │ ├── AppIcon-20@2x~ipad.png
│ │ │ ├── AppIcon-20@3x.png
│ │ │ ├── AppIcon-20~ipad.png
│ │ │ ├── AppIcon-29.png
│ │ │ ├── AppIcon-29@2x.png
│ │ │ ├── AppIcon-29@2x~ipad.png
│ │ │ ├── AppIcon-29@3x.png
│ │ │ ├── AppIcon-29~ipad.png
│ │ │ ├── AppIcon-40@2x.png
│ │ │ ├── AppIcon-40@2x~ipad.png
│ │ │ ├── AppIcon-40@3x.png
│ │ │ ├── AppIcon-40~ipad.png
│ │ │ ├── AppIcon-60@2x~car.png
│ │ │ ├── AppIcon-60@3x~car.png
│ │ │ ├── AppIcon-83.5@2x~ipad.png
│ │ │ ├── AppIcon@2x.png
│ │ │ ├── AppIcon@2x~ipad.png
│ │ │ ├── AppIcon@3x.png
│ │ │ ├── AppIcon~ios-marketing.png
│ │ │ ├── AppIcon~ipad.png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── app_icon_no-bg 1.png
│ │ │ ├── app_icon_no-bg 2.png
│ │ │ └── app_icon_no-bg.png
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── Runner-Bridging-Header.h
│ └── RunnerRelease.entitlements
├── RunnerTests
│ └── RunnerTests.swift
└── build
│ └── XCBuildData
│ └── 924928fd784c73820459c33f3f09eed1.xcbuilddata
│ ├── build-request.json
│ ├── description.msgpack
│ ├── manifest.json
│ ├── target-graph.txt
│ └── task-store.msgpack
├── lib
├── constants
│ ├── constants.dart
│ ├── library_list.dart
│ ├── themes
│ │ └── theme_color_data.dart
│ └── toolbar_items_list.dart
├── main.dart
├── providers
│ ├── editor_config_provider.dart
│ ├── navigation_provider.dart
│ ├── selecting_provider.dart
│ ├── settings_provider.dart
│ └── theme_provider.dart
├── ui
│ ├── components
│ │ ├── dialogs
│ │ │ ├── basic_popup.dart
│ │ │ ├── bottom_menu_popup.dart
│ │ │ ├── libraries_dialog.dart
│ │ │ ├── popup_list_dialog.dart
│ │ │ └── select_location.dart
│ │ ├── drawer.dart
│ │ ├── markdown
│ │ │ ├── build_markdown.dart
│ │ │ ├── editor_field.dart
│ │ │ ├── markdown_checkbox.dart
│ │ │ ├── rendering
│ │ │ │ ├── code_wrapper.dart
│ │ │ │ ├── custom_img_builder.dart
│ │ │ │ ├── custom_node.dart
│ │ │ │ ├── highlighter.dart
│ │ │ │ ├── html_support.dart
│ │ │ │ ├── latex.dart
│ │ │ │ ├── note_tags.dart
│ │ │ │ └── underline.dart
│ │ │ └── toolbar
│ │ │ │ ├── markdown_toolbar.dart
│ │ │ │ ├── modal_input_url.dart
│ │ │ │ ├── toolbar.dart
│ │ │ │ └── toolbar_item.dart
│ │ └── search_view.dart
│ ├── screens
│ │ ├── about
│ │ │ └── about_screen.dart
│ │ ├── archive
│ │ │ └── archive_screen.dart
│ │ ├── home
│ │ │ ├── intro_screen.dart
│ │ │ ├── main_scaffold.dart
│ │ │ ├── main_screen.dart
│ │ │ └── notes_display.dart
│ │ ├── layout
│ │ │ ├── grid_list_view.dart
│ │ │ └── tree_view.dart
│ │ ├── notes
│ │ │ ├── editor_config_screen.dart
│ │ │ ├── image_viewer.dart
│ │ │ ├── note_editor.dart
│ │ │ └── pdf_viewer.dart
│ │ ├── settings
│ │ │ ├── codeblock_theme_page.dart
│ │ │ ├── custom_theme_page.dart
│ │ │ └── settings_screen.dart
│ │ └── trash
│ │ │ └── trash_screen.dart
│ └── widgets
│ │ ├── custom_snackbar.dart
│ │ ├── file_info_bottom_sheet.dart
│ │ ├── list_section_title.dart
│ │ └── speed_dial_fab.dart
└── utils
│ ├── config_file
│ ├── custom_themes
│ │ ├── theme_json_handler.dart
│ │ └── theme_validator.dart
│ └── toolbar_config_handler.dart
│ ├── configs
│ ├── data_path.dart
│ ├── user_intro.dart
│ └── user_preference.dart
│ ├── file_info.dart
│ ├── handlers
│ ├── file_extensions.dart
│ ├── frontmatter_parser.dart
│ ├── item_archive.dart
│ ├── item_create.dart
│ ├── item_delete.dart
│ ├── item_duplication.dart
│ ├── item_move.dart
│ ├── item_rename.dart
│ └── item_sort.dart
│ ├── hex_color_extension.dart
│ ├── open_explorer.dart
│ └── storage_system.dart
├── linux
├── .gitignore
├── CMakeLists.txt
├── flutter
│ ├── CMakeLists.txt
│ ├── generated_plugin_registrant.cc
│ ├── generated_plugin_registrant.h
│ └── generated_plugins.cmake
├── main.cc
├── my_application.cc
└── my_application.h
├── macos
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── AppIcon_512.png
│ │ │ └── Contents.json
│ ├── Base.lproj
│ │ └── MainMenu.xib
│ ├── Configs
│ │ ├── AppInfo.xcconfig
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── Warnings.xcconfig
│ ├── DebugProfile.entitlements
│ ├── Info.plist
│ ├── MainFlutterWindow.swift
│ └── Release.entitlements
└── RunnerTests
│ └── RunnerTests.swift
├── pubspec.lock
├── pubspec.yaml
├── scripts
├── appimage-build.sh
├── docker-build.sh
└── get-hash.sh
└── windows
├── .gitignore
├── CMakeLists.txt
├── flutter
├── CMakeLists.txt
├── generated_plugin_registrant.cc
├── generated_plugin_registrant.h
└── generated_plugins.cmake
└── runner
├── CMakeLists.txt
├── Runner.rc
├── flutter_window.cpp
├── flutter_window.h
├── main.cpp
├── resource.h
├── resources
└── app_icon.png
├── runner.exe.manifest
├── utils.cpp
├── utils.h
├── win32_window.cpp
└── win32_window.h
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .build/
9 | .buildlog/
10 | .history
11 | .svn/
12 | .swiftpm/
13 | migrate_working_dir/
14 |
15 | .testing/
16 |
17 | # IntelliJ related
18 | *.iml
19 | *.ipr
20 | *.iws
21 | .idea/
22 |
23 | # The .vscode folder contains launch configuration and tasks you configure in
24 | # VS Code which you may wish to be included in version control, so this line
25 | # is commented out by default.
26 | #.vscode/
27 |
28 | # Flutter/Dart/Pub related
29 | **/doc/api/
30 | **/ios/Flutter/.last_build_id
31 | .dart_tool/
32 | .flutter-plugins
33 | .flutter-plugins-dependencies
34 | .pub-cache/
35 | .pub/
36 | /build/
37 |
38 | # AppImage related
39 | AppDir
40 | appimage-build
41 | *.AppImage*
42 |
43 | # Symbolication related
44 | app.*.symbols
45 |
46 | # Obfuscation related
47 | app.*.map.json
48 |
49 | # Android Studio will place build artifacts here
50 | /android/app/debug
51 | /android/app/profile
52 | /android/app/release
53 | /android/app/.cxx
54 |
55 | outputs/
56 | scripts/*.txt
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819"
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: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
17 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
18 | - platform: android
19 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
20 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
21 | - platform: ios
22 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
23 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
24 | - platform: linux
25 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
26 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
27 | - platform: macos
28 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
29 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
30 | - platform: web
31 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
32 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
33 | - platform: windows
34 | create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
35 | base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/AppImageBuilder.yml:
--------------------------------------------------------------------------------
1 | # appimage-builder recipe see https://appimage-builder.readthedocs.io for details
2 | version: 1
3 | script:
4 | - rm -rf AppDir || true
5 | - mkdir -p AppDir/usr/bin
6 | - cp -r build/linux/x64/release/bundle/* AppDir/usr/bin/
7 | AppDir:
8 | path: ./AppDir
9 | app_info:
10 | id: com.printnotes.printnotes
11 | name: Print(Notes)
12 | icon: application-vnd.appimage
13 | version: latest
14 | exec: usr/bin/printnotes
15 | exec_args: $@
16 | files:
17 | include: []
18 | exclude:
19 | - usr/share/man
20 | - usr/share/doc/*/README.*
21 | - usr/share/doc/*/changelog.*
22 | - usr/share/doc/*/NEWS.*
23 | - usr/share/doc/*/TODO.*
24 | test:
25 | fedora-30:
26 | image: appimagecrafters/tests-env:fedora-30
27 | command: ./AppRun
28 | debian-stable:
29 | image: appimagecrafters/tests-env:debian-stable
30 | command: ./AppRun
31 | archlinux-latest:
32 | image: appimagecrafters/tests-env:archlinux-latest
33 | command: ./AppRun
34 | centos-7:
35 | image: appimagecrafters/tests-env:centos-7
36 | command: ./AppRun
37 | ubuntu-xenial:
38 | image: appimagecrafters/tests-env:ubuntu-xenial
39 | command: ./AppRun
40 | AppImage:
41 | arch: x86_64
42 | update-information: guess
43 | file_name: 'AppDir/usr/bin/printnotes-x86_64.AppImage'
44 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct - Print(Notes)
2 |
3 | ## Our Standards
4 |
5 | Examples of behavior that contribute to a positive environment include:
6 |
7 | - Being welcoming, respectful, and inclusive to all contributors and users.
8 | - Providing constructive feedback in code reviews and discussions.
9 | - Being open to diverse viewpoints and ideas.
10 | - Showing empathy towards other members of the community.
11 |
12 | Examples of unacceptable behavior include:
13 |
14 | - Harassment, discrimination, or offensive comments (public or private).
15 | - Publishing others' private information without explicit consent.
16 | - Trolling, insults, or derogatory language.
17 | - Unwelcome sexual attention or advances.
18 | - Sustained disruption of discussions or collaboration.
19 |
20 | ## Our Responsibilities
21 |
22 | Project maintainers are responsible for:
23 |
24 | - Clarifying the standards of acceptable behavior and enforcing them in good faith.
25 | - Reviewing and addressing all reported issues or concerns promptly.
26 | - Maintaining confidentiality regarding reports of unacceptable behavior.
27 | - Removing, editing, or rejecting contributions that violate this Code of Conduct.
28 |
29 | ## Scope
30 |
31 | This Code of Conduct applies within all community spaces, and also applies when
32 | an individual is officially representing the community in public spaces.
33 | Examples of representing our community include using an official email address,
34 | posting via an official social media account, or acting as an appointed
35 | representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of unacceptable behavior may be reported by contacting me at **robot095@printnotes.app** or through any other means included in official sources. Reports will be reviewed and investigated, with outcomes communicated promptly.
40 |
41 | Actions maintainers may take include, but are not limited to:
42 |
43 | - A private warning or public apology.
44 | - Other actions deemed appropriate for the situation.
45 |
46 | All community leaders are obligated to respect the privacy and security of the
47 | reporter of any incident.
48 |
49 | ## Attribution
50 |
51 | This Code of Conduct is adapted from the [Contributor Covenant, version 2.1.](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
52 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ghcr.io/cirruslabs/flutter:3.29.2 AS build
2 |
3 | # Set up the working directory
4 | WORKDIR /app
5 |
6 | # Copy the project files into the container
7 | COPY . .
8 |
9 | # Install Android SDK and other dependencies for building APKs and AppImage
10 | RUN apt-get update && apt-get install -y \
11 | openjdk-17-jdk \
12 | libglu1-mesa \
13 | libssl-dev \
14 | wget \
15 | unzip \
16 | bash \
17 | git \
18 | build-essential \
19 | cmake \
20 | clang \
21 | ninja-build \
22 | pkg-config \
23 | libgtk-3-dev \
24 | libcurl4-openssl-dev \
25 | python3-dev
26 |
27 | # Accept android licenses
28 | RUN flutter doctor --android-licenses
29 |
30 | # Set Java 17 as the default version instead of 21
31 | RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-17-openjdk-amd64/bin/java 1
32 | RUN update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java
33 |
34 | # Ensure flutter is available and up-to-date
35 | RUN flutter doctor --verbose
36 |
37 | # Disable analytic reports
38 | RUN flutter config --no-analytics
39 |
40 | # Get all flutter dependencies
41 | RUN flutter pub get
42 |
43 | # Build the default APK
44 | RUN flutter build apk --release
45 |
46 | # Build the APKs for different ABIs (arm64-v8a, armeabi-v7a, x86_64)
47 | RUN flutter build apk --release --split-per-abi
48 |
49 | # Make output folder
50 | RUN mkdir -p /app/output
51 |
52 | # Move all apk files to output folder
53 | RUN cp build/app/outputs/flutter-apk/app-release.apk /app/output/
54 | RUN cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk /app/output/
55 | RUN cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk /app/output/
56 | RUN cp build/app/outputs/flutter-apk/app-x86_64-release.apk /app/output/
57 |
58 | # The default working directory inside the container
59 | CMD ["bash"]
--------------------------------------------------------------------------------
/FUNDING.yml:
--------------------------------------------------------------------------------
1 | liberapay: RoBoT_095
2 | buy_me_a_coffee: RoBoT_095
3 | open_collective: RoBoT_095
4 | ko_fi: robot095
5 |
--------------------------------------------------------------------------------
/PRIVACY_POLICY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy - Print(Notes)
2 |
3 | **Last Updated: Nov 23, 2024**
4 |
5 | Your privacy is important, so Print(Notes) does not collect, store, or share any data. This Privacy Policy explains how the app handles your information and internet usage.
6 |
7 | ## Information We Do Not Collect
8 |
9 | We do not:
10 |
11 | - Collect or store any user data.
12 | - Use analytics or tracking tools.
13 | - Access or process your personal information.
14 |
15 | ## Local Data Storage
16 |
17 | All notes and content you create in the app are stored locally on your device. The app does not send, upload, or back up this data to any external servers. You are fully in control of your notes and responsible for managing them.
18 |
19 | ## Internet Usage
20 |
21 | The app may request internet access solely for the purpose of:
22 |
23 | - Loading images or other embedded content from external URLs when rendered through Markdown or HTML.
24 |
25 | No additional data is transmitted, collected, or stored during this process. Be aware that embedded content is subject to the privacy policies of the external websites being accessed.
26 |
27 | ## Third-Party Services
28 |
29 | When embedding content from external websites (e.g., images, videos, or links), the privacy policies of those websites apply. The app does not interact with or process any of that content beyond retrieving and displaying it for you.
30 |
31 | If the app integrates with third-party services (e.g., Google Drive, Dropbox) for syncing, their privacy policies govern how your data is handled on their platforms. Be sure to review their policies before using such features, if and when they get added.
32 |
33 | ## Your Rights
34 |
35 | As this app does not collect any personal data, there is no data to access, modify, or delete. You are in complete control of your locally stored notes.
36 |
37 | ## Changes to This Privacy Policy
38 |
39 | This Privacy Policy may be updated periodically to reflect changes in the app or legal requirements. The latest version will always be accessible on the project’s GitHub repository.
40 |
41 | ## Contact Us
42 |
43 | If you have any questions or concerns about this Privacy Policy, feel free to reach out at **robot095@printnotes.app**
44 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at https://dart.dev/lints.
17 | #
18 | # Instead of disabling a lint rule for the entire project in the
19 | # section below, it can also be suppressed for a single line of code
20 | # or a specific dart file by using the `// ignore: name_of_lint` and
21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
22 | # producing the lint.
23 | rules:
24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26 |
27 | # Additional information about this file can be found at
28 | # https://dart.dev/guides/language/analysis-options
29 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | def keystoreProperties = new Properties()
26 | def keystorePropertiesFile = rootProject.file('key.properties')
27 | if (keystorePropertiesFile.exists()) {
28 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
29 | }
30 |
31 | android {
32 | namespace "com.example.printnotes"
33 | compileSdk flutter.compileSdkVersion
34 | ndkVersion flutter.ndkVersion
35 |
36 | compileOptions {
37 | sourceCompatibility JavaVersion.VERSION_1_8
38 | targetCompatibility JavaVersion.VERSION_1_8
39 | }
40 |
41 | kotlinOptions {
42 | jvmTarget = '1.8'
43 | }
44 |
45 | sourceSets {
46 | main.java.srcDirs += 'src/main/kotlin'
47 | }
48 |
49 | defaultConfig {
50 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
51 | applicationId "com.printnotes.printnotes"
52 | // You can update the following values to match your application needs.
53 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
54 | minSdkVersion flutter.minSdkVersion
55 | targetSdkVersion 34
56 | versionCode flutterVersionCode.toInteger()
57 | versionName flutterVersionName
58 | }
59 |
60 | signingConfigs {
61 | release {
62 | keyAlias = keystoreProperties['keyAlias']
63 | keyPassword = keystoreProperties['keyPassword']
64 | storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
65 | storePassword = keystoreProperties['storePassword']
66 | }
67 | }
68 |
69 | buildTypes {
70 | release {
71 | signingConfig = signingConfigs.release
72 | }
73 | }
74 |
75 | dependenciesInfo {
76 | // Disables dependency metadata when building APKs.
77 | includeInApk = false
78 | // Disables dependency metadata when building Android App Bundles.
79 | includeInBundle = false
80 | }
81 | }
82 |
83 | flutter {
84 | source '../..'
85 | }
86 |
87 | dependencies {}
88 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
16 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/printnotes/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.printnotes
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity()
6 |
--------------------------------------------------------------------------------
/android/app/src/main/play_store_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/play_store_512.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx4G
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/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-8.0-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }
9 | settings.ext.flutterSdkPath = flutterSdkPath()
10 |
11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
12 |
13 | repositories {
14 | google()
15 | mavenCentral()
16 | gradlePluginPortal()
17 | }
18 | }
19 |
20 | plugins {
21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
22 | id "com.android.application" version "8.1.0" apply false
23 | id "org.jetbrains.kotlin.android" version "1.9.0" apply false
24 | }
25 |
26 | include ":app"
27 |
--------------------------------------------------------------------------------
/assets/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/assets/app_icon.png
--------------------------------------------------------------------------------
/assets/app_icon_no-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/assets/app_icon_no-bg.png
--------------------------------------------------------------------------------
/assets/icons/ftp-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/assets/icons/ftp-icon.png
--------------------------------------------------------------------------------
/assets/icons/nextcloud-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/assets/icons/nextcloud-icon.png
--------------------------------------------------------------------------------
/assets/icons/rsync-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/assets/icons/rsync-icon.png
--------------------------------------------------------------------------------
/devtools_options.yaml:
--------------------------------------------------------------------------------
1 | extensions:
2 |
--------------------------------------------------------------------------------
/images/AllThemes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/AllThemes.png
--------------------------------------------------------------------------------
/images/AllThemes2-smaller.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/AllThemes2-smaller.png
--------------------------------------------------------------------------------
/images/Desktop/DesktopEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Desktop/DesktopEditor.png
--------------------------------------------------------------------------------
/images/Desktop/DesktopGridView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Desktop/DesktopGridView.png
--------------------------------------------------------------------------------
/images/Desktop/DesktopListView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Desktop/DesktopListView.png
--------------------------------------------------------------------------------
/images/Desktop/DesktopTreeView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Desktop/DesktopTreeView.png
--------------------------------------------------------------------------------
/images/Phone/IphoneEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/IphoneEditor.png
--------------------------------------------------------------------------------
/images/Phone/IphoneListView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/IphoneListView.png
--------------------------------------------------------------------------------
/images/Phone/PhoneAdvancedSearch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneAdvancedSearch.png
--------------------------------------------------------------------------------
/images/Phone/PhoneEditing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneEditing.png
--------------------------------------------------------------------------------
/images/Phone/PhoneEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneEditor.png
--------------------------------------------------------------------------------
/images/Phone/PhoneGridView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneGridView.png
--------------------------------------------------------------------------------
/images/Phone/PhoneListView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneListView.png
--------------------------------------------------------------------------------
/images/Phone/PhoneSettings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneSettings.png
--------------------------------------------------------------------------------
/images/Phone/PhoneTreeView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/PhoneTreeView.png
--------------------------------------------------------------------------------
/images/Phone/iPadEditing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/iPadEditing.png
--------------------------------------------------------------------------------
/images/Phone/iPadEditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/iPadEditor.png
--------------------------------------------------------------------------------
/images/Phone/iPadGridView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/iPadGridView.png
--------------------------------------------------------------------------------
/images/Phone/iPhoneEditing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/iPhoneEditing.png
--------------------------------------------------------------------------------
/images/Phone/iPhoneGridView.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Phone/iPhoneGridView.png
--------------------------------------------------------------------------------
/images/Wiki/Basics/home-fab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Basics/home-fab.png
--------------------------------------------------------------------------------
/images/Wiki/Basics/image-files.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Basics/image-files.png
--------------------------------------------------------------------------------
/images/Wiki/Basics/pdf-viewer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Basics/pdf-viewer.png
--------------------------------------------------------------------------------
/images/Wiki/Basics/web-image-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Basics/web-image-text.png
--------------------------------------------------------------------------------
/images/Wiki/Basics/web-images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Basics/web-images.png
--------------------------------------------------------------------------------
/images/Wiki/Search/search-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Search/search-example.png
--------------------------------------------------------------------------------
/images/Wiki/Search/search-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Search/search-view.png
--------------------------------------------------------------------------------
/images/Wiki/Settings/settings-layour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Settings/settings-layour.png
--------------------------------------------------------------------------------
/images/Wiki/Settings/settings-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Settings/settings-page.png
--------------------------------------------------------------------------------
/images/Wiki/Settings/settings-sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Settings/settings-sort.png
--------------------------------------------------------------------------------
/images/Wiki/Setup/init-allow-storage-access.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Setup/init-allow-storage-access.png
--------------------------------------------------------------------------------
/images/Wiki/Setup/init-directory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Setup/init-directory.png
--------------------------------------------------------------------------------
/images/Wiki/Setup/init-select-folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Setup/init-select-folder.png
--------------------------------------------------------------------------------
/images/Wiki/Setup/init-theme.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/Wiki/Setup/init-theme.png
--------------------------------------------------------------------------------
/images/play_store_feature_graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/images/play_store_feature_graphic.png
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 12.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '12.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | target 'RunnerTests' do
36 | inherit! :search_paths
37 | end
38 | end
39 |
40 | post_install do |installer|
41 | installer.pods_project.targets.each do |target|
42 | flutter_additional_ios_build_settings(target)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - DKImagePickerController/Core (4.3.9):
3 | - DKImagePickerController/ImageDataManager
4 | - DKImagePickerController/Resource
5 | - DKImagePickerController/ImageDataManager (4.3.9)
6 | - DKImagePickerController/PhotoGallery (4.3.9):
7 | - DKImagePickerController/Core
8 | - DKPhotoGallery
9 | - DKImagePickerController/Resource (4.3.9)
10 | - DKPhotoGallery (0.0.19):
11 | - DKPhotoGallery/Core (= 0.0.19)
12 | - DKPhotoGallery/Model (= 0.0.19)
13 | - DKPhotoGallery/Preview (= 0.0.19)
14 | - DKPhotoGallery/Resource (= 0.0.19)
15 | - SDWebImage
16 | - SwiftyGif
17 | - DKPhotoGallery/Core (0.0.19):
18 | - DKPhotoGallery/Model
19 | - DKPhotoGallery/Preview
20 | - SDWebImage
21 | - SwiftyGif
22 | - DKPhotoGallery/Model (0.0.19):
23 | - SDWebImage
24 | - SwiftyGif
25 | - DKPhotoGallery/Preview (0.0.19):
26 | - DKPhotoGallery/Model
27 | - DKPhotoGallery/Resource
28 | - SDWebImage
29 | - SwiftyGif
30 | - DKPhotoGallery/Resource (0.0.19):
31 | - SDWebImage
32 | - SwiftyGif
33 | - file_picker (0.0.1):
34 | - DKImagePickerController/PhotoGallery
35 | - Flutter
36 | - Flutter (1.0.0)
37 | - flutter_keyboard_visibility (0.0.1):
38 | - Flutter
39 | - path_provider_foundation (0.0.1):
40 | - Flutter
41 | - FlutterMacOS
42 | - pdfrx (0.0.3):
43 | - Flutter
44 | - FlutterMacOS
45 | - permission_handler_apple (9.3.0):
46 | - Flutter
47 | - SDWebImage (5.19.7):
48 | - SDWebImage/Core (= 5.19.7)
49 | - SDWebImage/Core (5.19.7)
50 | - shared_preferences_foundation (0.0.1):
51 | - Flutter
52 | - FlutterMacOS
53 | - SwiftyGif (5.4.5)
54 | - url_launcher_ios (0.0.1):
55 | - Flutter
56 |
57 | DEPENDENCIES:
58 | - file_picker (from `.symlinks/plugins/file_picker/ios`)
59 | - Flutter (from `Flutter`)
60 | - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
61 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
62 | - pdfrx (from `.symlinks/plugins/pdfrx/darwin`)
63 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
64 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
65 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
66 |
67 | SPEC REPOS:
68 | trunk:
69 | - DKImagePickerController
70 | - DKPhotoGallery
71 | - SDWebImage
72 | - SwiftyGif
73 |
74 | EXTERNAL SOURCES:
75 | file_picker:
76 | :path: ".symlinks/plugins/file_picker/ios"
77 | Flutter:
78 | :path: Flutter
79 | flutter_keyboard_visibility:
80 | :path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
81 | path_provider_foundation:
82 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
83 | pdfrx:
84 | :path: ".symlinks/plugins/pdfrx/darwin"
85 | permission_handler_apple:
86 | :path: ".symlinks/plugins/permission_handler_apple/ios"
87 | shared_preferences_foundation:
88 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
89 | url_launcher_ios:
90 | :path: ".symlinks/plugins/url_launcher_ios/ios"
91 |
92 | SPEC CHECKSUMS:
93 | DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
94 | DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
95 | file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
96 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
97 | flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
98 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
99 | pdfrx: 07fc287c47ea8d027c4ed56457f8a1aa74d73594
100 | permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
101 | SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
102 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
103 | SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
104 | url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
105 |
106 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
107 |
108 | COCOAPODS: 1.15.2
109 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
43 |
49 |
50 |
51 |
52 |
53 |
64 |
66 |
72 |
73 |
74 |
75 |
81 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "filename": "AppIcon@2x.png",
5 | "idiom": "iphone",
6 | "scale": "2x",
7 | "size": "60x60"
8 | },
9 | {
10 | "filename": "AppIcon@3x.png",
11 | "idiom": "iphone",
12 | "scale": "3x",
13 | "size": "60x60"
14 | },
15 | {
16 | "filename": "AppIcon~ipad.png",
17 | "idiom": "ipad",
18 | "scale": "1x",
19 | "size": "76x76"
20 | },
21 | {
22 | "filename": "AppIcon@2x~ipad.png",
23 | "idiom": "ipad",
24 | "scale": "2x",
25 | "size": "76x76"
26 | },
27 | {
28 | "filename": "AppIcon-83.5@2x~ipad.png",
29 | "idiom": "ipad",
30 | "scale": "2x",
31 | "size": "83.5x83.5"
32 | },
33 | {
34 | "filename": "AppIcon-40@2x.png",
35 | "idiom": "iphone",
36 | "scale": "2x",
37 | "size": "40x40"
38 | },
39 | {
40 | "filename": "AppIcon-40@3x.png",
41 | "idiom": "iphone",
42 | "scale": "3x",
43 | "size": "40x40"
44 | },
45 | {
46 | "filename": "AppIcon-40~ipad.png",
47 | "idiom": "ipad",
48 | "scale": "1x",
49 | "size": "40x40"
50 | },
51 | {
52 | "filename": "AppIcon-40@2x~ipad.png",
53 | "idiom": "ipad",
54 | "scale": "2x",
55 | "size": "40x40"
56 | },
57 | {
58 | "filename": "AppIcon-20@2x.png",
59 | "idiom": "iphone",
60 | "scale": "2x",
61 | "size": "20x20"
62 | },
63 | {
64 | "filename": "AppIcon-20@3x.png",
65 | "idiom": "iphone",
66 | "scale": "3x",
67 | "size": "20x20"
68 | },
69 | {
70 | "filename": "AppIcon-20~ipad.png",
71 | "idiom": "ipad",
72 | "scale": "1x",
73 | "size": "20x20"
74 | },
75 | {
76 | "filename": "AppIcon-20@2x~ipad.png",
77 | "idiom": "ipad",
78 | "scale": "2x",
79 | "size": "20x20"
80 | },
81 | {
82 | "filename": "AppIcon-29.png",
83 | "idiom": "iphone",
84 | "scale": "1x",
85 | "size": "29x29"
86 | },
87 | {
88 | "filename": "AppIcon-29@2x.png",
89 | "idiom": "iphone",
90 | "scale": "2x",
91 | "size": "29x29"
92 | },
93 | {
94 | "filename": "AppIcon-29@3x.png",
95 | "idiom": "iphone",
96 | "scale": "3x",
97 | "size": "29x29"
98 | },
99 | {
100 | "filename": "AppIcon-29~ipad.png",
101 | "idiom": "ipad",
102 | "scale": "1x",
103 | "size": "29x29"
104 | },
105 | {
106 | "filename": "AppIcon-29@2x~ipad.png",
107 | "idiom": "ipad",
108 | "scale": "2x",
109 | "size": "29x29"
110 | },
111 | {
112 | "filename": "AppIcon-60@2x~car.png",
113 | "idiom": "car",
114 | "scale": "2x",
115 | "size": "60x60"
116 | },
117 | {
118 | "filename": "AppIcon-60@3x~car.png",
119 | "idiom": "car",
120 | "scale": "3x",
121 | "size": "60x60"
122 | },
123 | {
124 | "filename": "AppIcon~ios-marketing.png",
125 | "idiom": "ios-marketing",
126 | "scale": "1x",
127 | "size": "1024x1024"
128 | }
129 | ],
130 | "info": {
131 | "author": "iconkitchen",
132 | "version": 1
133 | }
134 | }
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "app_icon_no-bg 2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "app_icon_no-bg 1.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "app_icon_no-bg.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg 1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg 2.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/Runner/Assets.xcassets/LaunchImage.imageset/app_icon_no-bg.png
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Print(Notes)
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | printnotes
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | NSDocumentsUsageDescription
30 | This app needs access to documents for storing and managing notes.
31 | NSPhotoLibraryUsageDescription
32 | This app requires access to the photo library.
33 | NSPhotoLibraryAddUsageDescription
34 | This app needs access to the photo library to save files.
35 | UIApplicationSupportsIndirectInputEvents
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIMainStoryboardFile
40 | Main
41 | UISupportedInterfaceOrientations
42 |
43 | UIInterfaceOrientationPortrait
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 | UIFileSharingEnabled
48 |
49 | LSSupportsOpeningDocumentsInPlace
50 |
51 | UISupportedInterfaceOrientations~ipad
52 |
53 | UIInterfaceOrientationPortrait
54 | UIInterfaceOrientationPortraitUpsideDown
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/ios/Runner/RunnerRelease.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/RunnerTests/RunnerTests.swift:
--------------------------------------------------------------------------------
1 | import Flutter
2 | import UIKit
3 | import XCTest
4 |
5 | class RunnerTests: XCTestCase {
6 |
7 | func testExample() {
8 | // If you add code to the Runner application, consider adding tests here.
9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/build-request.json:
--------------------------------------------------------------------------------
1 | {
2 | "buildCommand" : {
3 | "command" : "build",
4 | "skipDependencies" : false,
5 | "style" : "buildOnly"
6 | },
7 | "configuredTargets" : [
8 |
9 | ],
10 | "continueBuildingAfterErrors" : false,
11 | "dependencyScope" : "workspace",
12 | "enableIndexBuildArena" : false,
13 | "hideShellScriptEnvironment" : false,
14 | "parameters" : {
15 | "action" : "build",
16 | "overrides" : {
17 |
18 | }
19 | },
20 | "qos" : "utility",
21 | "schemeCommand" : "launch",
22 | "showNonLoggedProgress" : true,
23 | "useDryRun" : false,
24 | "useImplicitDependencies" : false,
25 | "useLegacyBuildLocations" : false,
26 | "useParallelTargets" : true
27 | }
--------------------------------------------------------------------------------
/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/description.msgpack:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/description.msgpack
--------------------------------------------------------------------------------
/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/manifest.json:
--------------------------------------------------------------------------------
1 | {"client":{"name":"basic","version":0,"file-system":"device-agnostic","perform-ownership-analysis":"no"},"targets":{"":[""]},"commands":{"":{"tool":"phony","inputs":[""],"outputs":[""]},"P0:::Gate WorkspaceHeaderMapVFSFilesWritten":{"tool":"phony","inputs":[],"outputs":[""]}}}
--------------------------------------------------------------------------------
/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/target-graph.txt:
--------------------------------------------------------------------------------
1 | Target dependency graph (0 target)
--------------------------------------------------------------------------------
/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/task-store.msgpack:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RoBoT095/printnotes/8ab3cb969e0b0ab55c5ab1f0a864e5b5ccc3104a/ios/build/XCBuildData/924928fd784c73820459c33f3f09eed1.xcbuilddata/task-store.msgpack
--------------------------------------------------------------------------------
/lib/constants/constants.dart:
--------------------------------------------------------------------------------
1 | // TODO: Don't forget to change app version here
2 | const String appVersion = '0.9.16';
3 |
4 | const allowedNoteExtensions = ['.md', '.markdown', '.txt', '.me'];
5 | const allowedImageExtensions = ['.jpg', '.jpeg', '.png'];
6 | const allowedPdfExtensions = ['.pdf'];
7 |
--------------------------------------------------------------------------------
/lib/constants/toolbar_items_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
3 |
4 | import 'package:printnotes/utils/config_file/toolbar_config_handler.dart';
5 |
6 | List defaultToolbarList = [
7 | ToolbarConfigItem(key: 'toolbar_view_item', visible: true),
8 | ToolbarConfigItem(key: 'toolbar_undo_redo_actions', visible: true),
9 | ToolbarConfigItem(key: 'toolbar_selection_action', visible: true),
10 | ToolbarConfigItem(key: 'toolbar_bold_action', visible: true),
11 | ToolbarConfigItem(key: 'toolbar_italic_action', visible: true),
12 | ToolbarConfigItem(key: 'toolbar_highlight_action', visible: true),
13 | ToolbarConfigItem(key: 'toolbar_strikethrough_action', visible: true),
14 | ToolbarConfigItem(key: 'toolbar_heading_action', visible: true),
15 | ToolbarConfigItem(key: 'toolbar_indent_action', visible: true),
16 | ToolbarConfigItem(key: 'toolbar_unindent_action', visible: true),
17 | ToolbarConfigItem(key: 'toolbar_unordered_list_action', visible: true),
18 | ToolbarConfigItem(key: 'toolbar_checkbox_list_action', visible: true),
19 | ToolbarConfigItem(key: 'toolbar_underline_action', visible: true),
20 | ToolbarConfigItem(key: 'toolbar_link_action', visible: true),
21 | ToolbarConfigItem(key: 'toolbar_image_action', visible: true),
22 | ToolbarConfigItem(key: 'toolbar_blockquote_action', visible: true),
23 | ToolbarConfigItem(key: 'toolbar_code_action', visible: true),
24 | ToolbarConfigItem(key: 'toolbar_line_action', visible: true),
25 | ];
26 |
27 | final Map> toolbarReference = {
28 | 'toolbar_view_item': {
29 | 'icon': FontAwesomeIcons.eye,
30 | 'text': 'Preview',
31 | },
32 | 'toolbar_undo_redo_actions': {
33 | 'icon': FontAwesomeIcons.arrowRotateLeft,
34 | 'text': 'Undo/Redo',
35 | },
36 | 'toolbar_selection_action': {
37 | 'icon': FontAwesomeIcons.textWidth,
38 | 'text': 'Select current line',
39 | },
40 | 'toolbar_bold_action': {
41 | 'icon': FontAwesomeIcons.bold,
42 | 'text': 'Make text bold',
43 | },
44 | 'toolbar_italic_action': {
45 | 'icon': FontAwesomeIcons.italic,
46 | 'text': 'Make text italic',
47 | },
48 | 'toolbar_highlight_action': {
49 | 'icon': FontAwesomeIcons.highlighter,
50 | 'text': 'Highlight text',
51 | },
52 | 'toolbar_strikethrough_action': {
53 | 'icon': FontAwesomeIcons.strikethrough,
54 | 'text': 'Strikethrough text',
55 | },
56 | 'toolbar_heading_action': {
57 | 'icon': FontAwesomeIcons.heading,
58 | 'text': 'Insert Heading',
59 | },
60 | 'toolbar_indent_action': {
61 | 'icon': Icons.format_indent_increase,
62 | 'text': 'Indent line',
63 | },
64 | 'toolbar_unindent_action': {
65 | 'icon': Icons.format_indent_decrease,
66 | 'text': 'Unindent line',
67 | },
68 | 'toolbar_unordered_list_action': {
69 | 'icon': FontAwesomeIcons.listUl,
70 | 'text': 'Unordered list',
71 | },
72 | 'toolbar_checkbox_list_action': {
73 | 'icon': FontAwesomeIcons.listCheck,
74 | 'text': 'Checkboxes',
75 | },
76 | 'toolbar_underline_action': {
77 | 'icon': FontAwesomeIcons.underline,
78 | 'text': 'Underline text',
79 | },
80 | 'toolbar_link_action': {
81 | 'icon': FontAwesomeIcons.link,
82 | 'text': 'Insert hyperlink',
83 | },
84 | 'toolbar_image_action': {
85 | 'icon': FontAwesomeIcons.image,
86 | 'text': 'Insert image',
87 | },
88 | 'toolbar_blockquote_action': {
89 | 'icon': FontAwesomeIcons.quoteLeft,
90 | 'text': 'Blockquote',
91 | },
92 | 'toolbar_code_action': {
93 | 'icon': FontAwesomeIcons.code,
94 | 'text': 'Code syntax/font',
95 | },
96 | 'toolbar_line_action': {
97 | 'icon': FontAwesomeIcons.rulerHorizontal,
98 | 'text': 'Add horizontal line',
99 | },
100 | };
101 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:path_provider/path_provider.dart';
5 | import 'package:permission_handler/permission_handler.dart';
6 | import 'package:provider/provider.dart';
7 |
8 | import 'package:printnotes/providers/theme_provider.dart';
9 | import 'package:printnotes/providers/settings_provider.dart';
10 | import 'package:printnotes/providers/navigation_provider.dart';
11 | import 'package:printnotes/providers/editor_config_provider.dart';
12 | import 'package:printnotes/providers/selecting_provider.dart';
13 |
14 | import 'package:printnotes/utils/configs/data_path.dart';
15 | import 'package:printnotes/utils/configs/user_intro.dart';
16 | import 'package:printnotes/ui/screens/home/main_screen.dart';
17 |
18 | void main() {
19 | WidgetsFlutterBinding.ensureInitialized();
20 | runApp(
21 | MultiProvider(
22 | providers: [
23 | ChangeNotifierProvider(create: (_) => ThemeProvider()),
24 | ChangeNotifierProvider(create: (_) => SettingsProvider()),
25 | ChangeNotifierProvider(create: (_) => NavigationProvider()),
26 | ChangeNotifierProvider(create: (_) => EditorConfigProvider()),
27 | ChangeNotifierProvider(create: (_) => SelectingProvider()),
28 | ],
29 | child: const App(),
30 | ),
31 | );
32 | }
33 |
34 | class App extends StatefulWidget {
35 | const App({super.key});
36 |
37 | @override
38 | State createState() => _AppState();
39 | }
40 |
41 | class _AppState extends State {
42 | @override
43 | void initState() {
44 | super.initState();
45 | _checkStorageAccess();
46 | }
47 |
48 | void _checkStorageAccess() async {
49 | final String? mainDir = await DataPath.selectedDirectory;
50 | final Directory defaultDir = await getApplicationDocumentsDirectory();
51 | final bool isNewUser = await UserFirstTime.getShowIntro;
52 |
53 | if (mainDir != null) {
54 | if (Platform.isAndroid &&
55 | isNewUser != true &&
56 | mainDir != defaultDir.path) {
57 | final status = await Permission.manageExternalStorage.request();
58 | if (!status.isGranted) {
59 | throw "Please allow storage permission to access files";
60 | }
61 | }
62 | }
63 | }
64 |
65 | @override
66 | Widget build(BuildContext context) {
67 | return Consumer(
68 | builder: (context, themeProvider, child) {
69 | return MaterialApp(
70 | debugShowCheckedModeBanner: false,
71 | theme: ThemeData(colorScheme: themeProvider.getThemeData(context)),
72 | themeMode: themeProvider.themeMode,
73 | title: 'Print(Notes)',
74 | home: const MainPage(
75 | title: 'Print(Notes)',
76 | ),
77 | );
78 | },
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/lib/providers/editor_config_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'package:printnotes/constants/toolbar_items_list.dart';
4 | import 'package:printnotes/utils/config_file/toolbar_config_handler.dart';
5 | import 'package:printnotes/utils/configs/user_preference.dart';
6 |
7 | class EditorConfigProvider with ChangeNotifier {
8 | double _fontSize = 16;
9 | bool _isEditing = false;
10 | List _toolbarItemList = [];
11 |
12 | double get fontSize => _fontSize;
13 | bool get isEditing => _isEditing;
14 | List get toolbarItemList => _toolbarItemList;
15 |
16 | EditorConfigProvider() {
17 | loadEditorConfig();
18 | }
19 |
20 | void loadEditorConfig() async {
21 | final fontSize = await UserEditorConfig.getFontSize();
22 | final toolbarConfig = loadToolbarLoadout();
23 |
24 | setFontSize(fontSize);
25 | setToolbarConfig(toolbarConfig);
26 | }
27 |
28 | void setFontSize(double fontSize) {
29 | _fontSize = fontSize;
30 | UserEditorConfig.setFontSize(fontSize);
31 | notifyListeners();
32 | }
33 |
34 | void setIsEditing(bool value) {
35 | _isEditing = value;
36 | notifyListeners();
37 | }
38 |
39 | void setToolbarConfig(List? configList) {
40 | if (configList == null ||
41 | configList.isEmpty ||
42 | configList.length != defaultToolbarList.length) {
43 | _toolbarItemList = defaultToolbarList;
44 | } else {
45 | _toolbarItemList = configList;
46 | }
47 | saveToolbarLoadout(_toolbarItemList);
48 | notifyListeners();
49 | }
50 |
51 | void setToolbarItemVisibility(bool val, int index) {
52 | _toolbarItemList[index].visible = val;
53 | saveToolbarLoadout(_toolbarItemList);
54 | notifyListeners();
55 | }
56 |
57 | void updateListOrder(int oldIndex, int newIndex) {
58 | if (oldIndex < newIndex) {
59 | newIndex -= 1;
60 | }
61 | final toolbarItem = _toolbarItemList.removeAt(oldIndex);
62 | _toolbarItemList.insert(newIndex, toolbarItem);
63 |
64 | saveToolbarLoadout(_toolbarItemList);
65 | notifyListeners();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/providers/navigation_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:file_picker/file_picker.dart';
5 |
6 | import 'package:printnotes/utils/handlers/file_extensions.dart';
7 | import 'package:printnotes/ui/screens/notes/image_viewer.dart';
8 | import 'package:printnotes/ui/screens/notes/note_editor.dart';
9 | import 'package:printnotes/ui/screens/notes/pdf_viewer.dart';
10 | // import 'package:printnotes/ui/widgets/custom_snackbar.dart';
11 |
12 | class NavigationProvider with ChangeNotifier {
13 | final List _routeHistory = [];
14 |
15 | List get routeHistory => _routeHistory;
16 |
17 | void initRouteHistory(String initDir) {
18 | if (_routeHistory.isEmpty) {
19 | _routeHistory.add(initDir);
20 | } else if (_routeHistory.first != initDir) {
21 | _routeHistory.clear();
22 | _routeHistory.add(initDir);
23 | }
24 | }
25 |
26 | void addToRouteHistory(String route) {
27 | // if main route was passed, reset history to main route
28 | if (_routeHistory.first == route) {
29 | _routeHistory.clear();
30 | _routeHistory.add(route);
31 | notifyListeners();
32 | }
33 | // Prevent the addition of the same route to list again
34 | if (_routeHistory.last != route) {
35 | _routeHistory.add(route);
36 | }
37 | }
38 |
39 | // Removes last entry in folder history and return path of previous location
40 | String? navigateBack() {
41 | if (_routeHistory.length > 1) {
42 | _routeHistory.removeLast();
43 | notifyListeners();
44 | return _routeHistory.last;
45 | }
46 |
47 | return null;
48 | }
49 |
50 | void routeItemToPage(BuildContext context, FileSystemEntity item) {
51 | if (item is File) {
52 | if (fileTypeChecker(item) == CFileType.image) {
53 | onImageSelect(context, item);
54 | } else if (fileTypeChecker(item) == CFileType.pdf) {
55 | onPdfSelect(context, item);
56 | } else {
57 | onNoteSelect(context, item);
58 | }
59 | }
60 | }
61 |
62 | // For notes only, won't work with folders
63 | void onNoteSelect(
64 | BuildContext context,
65 | File item,
66 | ) {
67 | addToRouteHistory(item.path);
68 | Navigator.push(
69 | context,
70 | MaterialPageRoute(
71 | builder: (context) => NoteEditorScreen(
72 | filePath: item.path,
73 | ),
74 | ),
75 | ).then((_) => navigateBack());
76 | }
77 |
78 | void onImageSelect(
79 | BuildContext context,
80 | File item,
81 | ) {
82 | addToRouteHistory(item.path);
83 | Navigator.push(
84 | context,
85 | MaterialPageRoute(
86 | builder: (context) => ImageViewScreen(imageFile: item)))
87 | .then((_) => navigateBack());
88 | }
89 |
90 | void onPdfSelect(
91 | BuildContext context,
92 | File item,
93 | ) {
94 | addToRouteHistory(item.path);
95 | Navigator.push(
96 | context,
97 | MaterialPageRoute(
98 | builder: (context) => PdfViewScreen(pdfFile: item)))
99 | .then((_) => navigateBack());
100 | }
101 |
102 | // Open files outside selected app directory
103 | Future openExternalFile(BuildContext context) async {
104 | FilePickerResult? selectedFile = await FilePicker.platform.pickFiles(
105 | allowMultiple: false,
106 | type: FileType.custom,
107 | allowedExtensions: ['jpg', 'jpeg', 'png', 'pdf'],
108 | );
109 | if (selectedFile != null && context.mounted) {
110 | File item = File(selectedFile.files.single.path!);
111 | routeItemToPage(context, item);
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/lib/providers/selecting_provider.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | import 'package:printnotes/utils/storage_system.dart';
6 |
7 | class SelectingProvider with ChangeNotifier {
8 | bool _selectingMode = false;
9 | final List _selectedItems = [];
10 |
11 | bool get selectingMode => _selectingMode;
12 | List get selectedItems => _selectedItems;
13 |
14 | void updateSelectedList(FileSystemEntity item) {
15 | if (_selectedItems.contains(item.path)) {
16 | _selectedItems.remove(item.path);
17 | } else {
18 | _selectedItems.add(item.path);
19 | }
20 | notifyListeners();
21 | }
22 |
23 | void setSelectingMode({bool? mode}) {
24 | if (mode != null) {
25 | _selectingMode = mode;
26 | if (!mode) _selectedItems.clear();
27 | } else {
28 | _selectingMode = !_selectingMode;
29 | }
30 | notifyListeners();
31 | }
32 |
33 | void selectAll(String dir) {
34 | final List items = StorageSystem.listFolderContents(dir);
35 | List itemPathsList = [];
36 | for (var item in items) {
37 | if (item is File) itemPathsList.add(item.path);
38 | }
39 |
40 | // If ALL already selected, then deselect all
41 | if (Set.of(_selectedItems).containsAll(itemPathsList)) {
42 | _selectedItems.clear();
43 | } else {
44 | // else select all
45 | for (var item in itemPathsList) {
46 | if (!_selectedItems.contains(item)) {
47 | _selectedItems.add(item);
48 | }
49 | }
50 | }
51 | notifyListeners();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/components/dialogs/basic_popup.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | Future showBasicPopup(
4 | BuildContext context, String title, String content) async {
5 | return await showDialog(
6 | context: context,
7 | builder: (context) => AlertDialog(
8 | title: Text(title),
9 | content: Text(content),
10 | actions: [
11 | TextButton(
12 | onPressed: () => Navigator.of(context).pop(false),
13 | child: Text(
14 | 'No',
15 | style:
16 | TextStyle(color: Theme.of(context).colorScheme.secondary),
17 | ),
18 | ),
19 | TextButton(
20 | style: TextButton.styleFrom(
21 | backgroundColor: Theme.of(context).colorScheme.secondary,
22 | foregroundColor: Theme.of(context).colorScheme.onSecondary,
23 | ),
24 | onPressed: () => Navigator.of(context).pop(true),
25 | child: const Text('Yes'),
26 | ),
27 | ],
28 | ),
29 | ) ??
30 | false;
31 | }
32 |
--------------------------------------------------------------------------------
/lib/ui/components/dialogs/libraries_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/url_launcher.dart';
3 |
4 | import 'package:printnotes/constants/library_list.dart';
5 |
6 | launchURL(String urlString) async {
7 | Uri url = Uri.parse(urlString);
8 | try {
9 | await launchUrl(url);
10 | } catch (e) {
11 | throw 'Could not launch $url with error $e';
12 | }
13 | }
14 |
15 | void showLibrariesDialog(BuildContext context) {
16 | showDialog(
17 | context: context,
18 | builder: (BuildContext context) {
19 | return AlertDialog(
20 | title: const Text('Libraries'),
21 | icon: const Icon(
22 | Icons.handshake,
23 | size: 40,
24 | ),
25 | content: SizedBox(
26 | width: double.maxFinite,
27 | child: ListView.builder(
28 | shrinkWrap: true,
29 | itemCount: libraries.length,
30 | itemBuilder: (BuildContext context, int index) {
31 | return Card(
32 | child: ListTile(
33 | title: InkWell(
34 | onTap: () {
35 | launchURL(libraries[index].url);
36 | },
37 | child: Text(
38 | '${libraries[index].name} by ${libraries[index].publisher}'),
39 | ),
40 | subtitle: Text(
41 | libraries[index].license,
42 | style: TextStyle(
43 | color: Theme.of(context)
44 | .colorScheme
45 | .onSurface
46 | .withOpacity(0.5)),
47 | ),
48 | ),
49 | );
50 | },
51 | ),
52 | ),
53 | actions: [
54 | TextButton(
55 | child: Text(
56 | 'Close',
57 | style: TextStyle(color: Theme.of(context).colorScheme.secondary),
58 | ),
59 | onPressed: () {
60 | Navigator.of(context).pop();
61 | },
62 | ),
63 | ],
64 | );
65 | },
66 | );
67 | }
68 |
--------------------------------------------------------------------------------
/lib/ui/components/dialogs/popup_list_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | void showRadioListDialog(
4 | BuildContext context, {
5 | Widget? tileTitle,
6 | required String selectedItem,
7 | required List items,
8 | required ValueChanged onUpdated,
9 | }) {
10 | showDialog(
11 | context: context,
12 | builder: (BuildContext context) {
13 | return AlertDialog(
14 | title: tileTitle,
15 | content: SizedBox(
16 | width: double.maxFinite,
17 | child: ListView.builder(
18 | shrinkWrap: true,
19 | itemCount: items.length,
20 | itemBuilder: (BuildContext context, int index) {
21 | return PopupMenuItem(
22 | child: RadioListTile(
23 | title: Text(items[index],
24 | style: const TextStyle(
25 | fontSize: 16, fontWeight: FontWeight.normal)),
26 | value: items[index],
27 | groupValue: selectedItem,
28 | onChanged: (value) {
29 | selectedItem = value!;
30 | onUpdated(value);
31 | Navigator.of(context).pop();
32 | },
33 | ),
34 | );
35 | },
36 | ),
37 | ),
38 | actions: [
39 | TextButton(
40 | child: Text(
41 | 'Done',
42 | style: TextStyle(color: Theme.of(context).colorScheme.secondary),
43 | ),
44 | onPressed: () => Navigator.of(context).pop(),
45 | ),
46 | ],
47 | );
48 | },
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/lib/ui/components/dialogs/select_location.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/material.dart';
3 | import 'package:path/path.dart' as path;
4 | import 'package:printnotes/utils/storage_system.dart';
5 |
6 | class SelectLocationDialog extends StatefulWidget {
7 | const SelectLocationDialog({
8 | super.key,
9 | required this.baseDir,
10 | required this.items,
11 | });
12 |
13 | final String baseDir;
14 | final List items;
15 |
16 | @override
17 | State createState() => _SelectLocationDialogState();
18 | }
19 |
20 | class _SelectLocationDialogState extends State {
21 | late String _currentDir;
22 | List _directories = [];
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 | _currentDir = widget.baseDir;
28 | _loadDirectories();
29 | }
30 |
31 | void _loadDirectories() {
32 | final contents = StorageSystem.listFolderContents(_currentDir);
33 | setState(() {
34 | _directories = contents.whereType().toList();
35 | // Removes the item if it's a directory to prevent moving into itself
36 | if (widget.items.length == 1) {
37 | var item = widget.items.first;
38 | if (item is Directory) {
39 | _directories.removeWhere((dir) => dir.path == item.path);
40 | }
41 | }
42 | });
43 | }
44 |
45 | void _navigateUp() {
46 | if (_currentDir != widget.baseDir) {
47 | setState(() {
48 | _currentDir = path.dirname(_currentDir);
49 | _loadDirectories();
50 | });
51 | }
52 | }
53 |
54 | void _navigateInto(Directory dir) {
55 | setState(() {
56 | _currentDir = dir.path;
57 | _loadDirectories();
58 | });
59 | }
60 |
61 | @override
62 | Widget build(BuildContext context) {
63 | return AlertDialog(
64 | title: const Text('Select destination:'),
65 | content: SizedBox(
66 | width: double.maxFinite,
67 | child: Column(
68 | mainAxisSize: MainAxisSize.min,
69 | children: [
70 | if (_currentDir == widget.baseDir)
71 | ListTile(
72 | leading: const Icon(Icons.arrow_forward),
73 | title: Text('./${path.basename(_currentDir)}'),
74 | ),
75 | // When entering a folder, shows '../folder_name' button to return
76 | if (_currentDir != widget.baseDir)
77 | ListTile(
78 | leading: const Icon(Icons.arrow_back),
79 | title: Text('../${path.basename(_currentDir)}'),
80 | onTap: _navigateUp,
81 | ),
82 | Expanded(
83 | child: ListView.builder(
84 | itemCount: _directories.length,
85 | itemBuilder: (context, index) {
86 | final dir = _directories[index];
87 | return ListTile(
88 | leading: Icon(
89 | Icons.folder,
90 | color: Theme.of(context).colorScheme.secondary,
91 | ),
92 | title: Text(path.basename(dir.path)),
93 | onTap: () => _navigateInto(dir as Directory),
94 | );
95 | },
96 | ),
97 | ),
98 | // If no folders in directory, show message
99 | if (_directories.isEmpty)
100 | const Expanded(
101 | child: Text('No further folders here!'),
102 | ),
103 | ],
104 | ),
105 | ),
106 | actions: [
107 | TextButton(
108 | child: const Text('Cancel'),
109 | onPressed: () => Navigator.of(context).pop(),
110 | ),
111 | TextButton(
112 | child: const Text('Move here'),
113 | onPressed: () => Navigator.of(context).pop(_currentDir),
114 | ),
115 | ],
116 | );
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/editor_field.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class EditorField extends StatelessWidget {
4 | const EditorField({
5 | super.key,
6 | required this.controller,
7 | required this.onChanged,
8 | this.undoController,
9 | this.fontSize = 16,
10 | });
11 |
12 | final TextEditingController controller;
13 | final Function(String) onChanged;
14 | final UndoHistoryController? undoController;
15 | final double fontSize;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return TextField(
20 | controller: controller,
21 | maxLines: null,
22 | keyboardType: TextInputType.multiline,
23 | autofocus: false,
24 | onChanged: onChanged,
25 | undoController: undoController,
26 | style: TextStyle(fontSize: fontSize),
27 | decoration: InputDecoration(
28 | border: InputBorder.none,
29 | hintText: 'Type something...',
30 | hintStyle: TextStyle(color: Theme.of(context).hintColor)),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/markdown_checkbox.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | Widget markdownCheckBox(bool checked, double? size) {
4 | return SizedBox(
5 | width: size ?? 20,
6 | height: size ?? 20,
7 | child: Checkbox(
8 | value: checked,
9 | hoverColor: null,
10 | focusColor: null,
11 | splashRadius: 0,
12 | onChanged: (value) {},
13 | ),
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/code_wrapper.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Code is from asjqkkkk/markdown_widget examples, modified it a bit
3 | // https://github.com/asjqkkkk/markdown_widget/blob/master/example/lib/widget/code_wrapper.dart
4 | //
5 |
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter/services.dart';
8 |
9 | class CodeWrapperWidget extends StatefulWidget {
10 | const CodeWrapperWidget(
11 | this.child,
12 | this.text,
13 | this.language, {
14 | super.key,
15 | this.hideCodeButtons,
16 | });
17 |
18 | final Widget child;
19 | final String text;
20 | final String language;
21 | final bool? hideCodeButtons;
22 |
23 | @override
24 | State createState() => _PreWrapperState();
25 | }
26 |
27 | class _PreWrapperState extends State {
28 | late Widget _switchWidget;
29 | bool hasCopied = false;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | _switchWidget = Icon(Icons.copy_rounded, key: UniqueKey());
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | final isDark = Theme.of(context).brightness == Brightness.dark;
40 | return Stack(
41 | children: [
42 | widget.child,
43 | Align(
44 | alignment: Alignment.topRight,
45 | child: Container(
46 | padding: const EdgeInsets.all(16.0),
47 | child: widget.hideCodeButtons ?? false
48 | ? null
49 | : Row(
50 | mainAxisSize: MainAxisSize.min,
51 | children: [
52 | if (widget.language.isNotEmpty)
53 | SelectionContainer.disabled(
54 | child: Container(
55 | margin: const EdgeInsets.only(right: 2),
56 | padding: const EdgeInsets.all(2),
57 | decoration: BoxDecoration(
58 | borderRadius: BorderRadius.circular(4),
59 | border: Border.all(
60 | width: 0.5,
61 | color:
62 | isDark ? Colors.white : Colors.black)),
63 | child: Text(widget.language),
64 | )),
65 | InkWell(
66 | child: AnimatedSwitcher(
67 | duration: const Duration(milliseconds: 200),
68 | child: _switchWidget,
69 | ),
70 | onTap: () async {
71 | if (hasCopied) return;
72 | await Clipboard.setData(
73 | ClipboardData(text: widget.text));
74 | _switchWidget = Icon(Icons.check, key: UniqueKey());
75 | refresh();
76 | Future.delayed(const Duration(seconds: 2), () {
77 | hasCopied = false;
78 | _switchWidget =
79 | Icon(Icons.copy_rounded, key: UniqueKey());
80 | refresh();
81 | });
82 | },
83 | ),
84 | ],
85 | )),
86 | )
87 | ],
88 | );
89 | }
90 |
91 | void refresh() {
92 | if (mounted) setState(() {});
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/custom_img_builder.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:path/path.dart';
5 | import 'package:provider/provider.dart';
6 |
7 | import 'package:printnotes/providers/settings_provider.dart';
8 | import 'package:printnotes/providers/editor_config_provider.dart';
9 | import 'package:printnotes/utils/storage_system.dart';
10 |
11 | class CustomImgBuilder extends StatelessWidget {
12 | final String url;
13 | final Map attributes;
14 |
15 | const CustomImgBuilder(this.url, this.attributes, {super.key});
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | double editorFontSize = context.watch().fontSize;
20 |
21 | File getLocalImage() {
22 | final allFiles = StorageSystem.listFolderContents(
23 | context.read().mainDir,
24 | recursive: true,
25 | showHidden: true,
26 | );
27 |
28 | for (var item in allFiles) {
29 | if (item is File && basename(item.path) == url) {
30 | return File(item.path);
31 | }
32 | }
33 | return File(url);
34 | }
35 |
36 | Widget errorMessage(String text) => Padding(
37 | padding: const EdgeInsets.all(8.0),
38 | child: RichText(
39 | softWrap: true,
40 | maxLines: 3,
41 | text:
42 | TextSpan(style: TextStyle(fontSize: editorFontSize), children: [
43 | WidgetSpan(
44 | child: Tooltip(
45 | triggerMode: TooltipTriggerMode.tap,
46 | showDuration: const Duration(seconds: 5),
47 | enableTapToDismiss: true,
48 | decoration: BoxDecoration(
49 | color: Theme.of(context).colorScheme.error.withOpacity(0.5),
50 | ),
51 | message: text,
52 | textStyle:
53 | TextStyle(color: Theme.of(context).colorScheme.onSurface),
54 | child: Icon(
55 | Icons.broken_image,
56 | color: Theme.of(context).colorScheme.error,
57 | ),
58 | ),
59 | ),
60 | TextSpan(
61 | text: ' ${attributes['alt'] ?? ''}',
62 | style:
63 | TextStyle(color: Theme.of(context).colorScheme.onSurface),
64 | ),
65 | ]),
66 | ),
67 | );
68 |
69 | if (url.startsWith('http')) {
70 | return Image.network(
71 | url,
72 | fit: BoxFit.cover,
73 | loadingBuilder: (context, child, loadingProgress) {
74 | if (loadingProgress == null) return child;
75 | return Center(
76 | child: CircularProgressIndicator(
77 | value: loadingProgress.expectedTotalBytes != null
78 | ? loadingProgress.cumulativeBytesLoaded /
79 | loadingProgress.expectedTotalBytes!
80 | : null,
81 | ),
82 | );
83 | },
84 | errorBuilder: (context, error, stackTrace) =>
85 | errorMessage('Image could not be loaded'),
86 | );
87 | } else {
88 | return Image.file(
89 | getLocalImage(),
90 | fit: BoxFit.cover,
91 | errorBuilder: (context, error, stackTrace) => errorMessage(
92 | 'Incorrect path or file type, check if url is correct'),
93 | );
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/custom_node.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Code is from asjqkkkk/markdown_widget examples, modified it a bit
3 | // https://github.com/asjqkkkk/markdown_widget/blob/dev/example/lib/markdown_custom/custom_node.dart
4 | //
5 |
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';
8 | import 'package:markdown_widget/markdown_widget.dart';
9 | import 'package:markdown/markdown.dart' as m;
10 |
11 | import 'html_support.dart';
12 |
13 | class CustomTextNode extends ElementNode {
14 | final String text;
15 | final MarkdownConfig config;
16 | final WidgetVisitor visitor;
17 | bool isTable = false;
18 |
19 | CustomTextNode(this.text, this.config, this.visitor);
20 |
21 | @override
22 | InlineSpan build() {
23 | if (isTable) {
24 | //deal complex table tag with html core widget
25 | return WidgetSpan(
26 | child: HtmlWidget(text),
27 | );
28 | } else {
29 | return super.build();
30 | }
31 | }
32 |
33 | @override
34 | void onAccepted(SpanNode parent) {
35 | final textStyle = config.p.textStyle.merge(parentStyle);
36 | children.clear();
37 | if (!text.contains(htmlRep)) {
38 | accept(TextNode(text: text, style: textStyle));
39 | return;
40 | }
41 | //Intercept as table tag
42 | if (text.contains(tableRep)) {
43 | isTable = true;
44 | accept(parent);
45 | return;
46 | }
47 |
48 | //The remaining ones are processed by the regular HTML processing.
49 | final spans = parseHtml(
50 | m.Text(text),
51 | visitor: WidgetVisitor(
52 | config: visitor.config,
53 | generators: visitor.generators,
54 | richTextBuilder: visitor.richTextBuilder,
55 | ),
56 | parentStyle: parentStyle,
57 | );
58 | for (var element in spans) {
59 | isTable = false;
60 | accept(element);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/highlighter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:markdown_widget/markdown_widget.dart';
3 | import 'package:markdown/markdown.dart' as m;
4 |
5 | const _highlighterTag = 'highlighter';
6 |
7 | class HighlighterNode extends SpanNode {
8 | final Map attribute;
9 | final MarkdownConfig config;
10 |
11 | String get highlightId => attribute['id'] ?? '';
12 |
13 | HighlighterNode(this.attribute, this.config);
14 |
15 | @override
16 | InlineSpan build() => TextSpan(
17 | text: highlightId,
18 | style: config.p.textStyle.merge(parentStyle?.copyWith(
19 | backgroundColor: const Color.fromARGB(148, 255, 225, 118))),
20 | );
21 | }
22 |
23 | SpanNodeGeneratorWithTag highlighterGeneratorWithTag = SpanNodeGeneratorWithTag(
24 | tag: _highlighterTag,
25 | generator: (e, config, visitor) => HighlighterNode(e.attributes, config));
26 |
27 | class HighlighterSyntax extends m.InlineSyntax {
28 | HighlighterSyntax() : super(r'==(.*?)==');
29 |
30 | @override
31 | bool onMatch(m.InlineParser parser, Match match) {
32 | m.Element el = m.Element.withTag(_highlighterTag);
33 | final input = match.input;
34 | el.attributes['id'] = input.substring(match.start + 2, match.end - 2);
35 | parser.addNode(el);
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/latex.dart:
--------------------------------------------------------------------------------
1 | //
2 | // Code is from asjqkkkk/markdown_widget examples, modified it a bit
3 | // https://github.com/asjqkkkk/markdown_widget/blob/dev/example/lib/markdown_custom/latex.dart
4 | //
5 |
6 | import 'package:flutter/material.dart';
7 | import 'package:markdown_widget/markdown_widget.dart';
8 | import 'package:flutter_math_fork/flutter_math.dart';
9 | import 'package:markdown/markdown.dart' as m;
10 |
11 | SpanNodeGeneratorWithTag latexGenerator = SpanNodeGeneratorWithTag(
12 | tag: _latexTag,
13 | generator: (e, config, visitor) =>
14 | LatexNode(e.attributes, e.textContent, config));
15 |
16 | const _latexTag = 'latex';
17 |
18 | class LatexSyntax extends m.InlineSyntax {
19 | LatexSyntax() : super(r'(\$\$[\s\S]+\$\$)|(\$.+?\$)');
20 |
21 | @override
22 | bool onMatch(m.InlineParser parser, Match match) {
23 | final input = match.input;
24 | final matchValue = input.substring(match.start, match.end);
25 | String content = '';
26 | bool isInline = true;
27 | const blockSyntax = '\$\$';
28 | const inlineSyntax = '\$';
29 | if (matchValue.startsWith(blockSyntax) &&
30 | matchValue.endsWith(blockSyntax) &&
31 | (matchValue != blockSyntax)) {
32 | content = matchValue.substring(2, matchValue.length - 2);
33 | isInline = false;
34 | } else if (matchValue.startsWith(inlineSyntax) &&
35 | matchValue.endsWith(inlineSyntax) &&
36 | matchValue != inlineSyntax) {
37 | content = matchValue.substring(1, matchValue.length - 1);
38 | }
39 | m.Element el = m.Element.text(_latexTag, matchValue);
40 | el.attributes['content'] = content;
41 | el.attributes['isInline'] = '$isInline';
42 | parser.addNode(el);
43 | return true;
44 | }
45 | }
46 |
47 | class LatexNode extends SpanNode {
48 | final Map attributes;
49 | final String textContent;
50 | final MarkdownConfig config;
51 |
52 | LatexNode(this.attributes, this.textContent, this.config);
53 |
54 | @override
55 | InlineSpan build() {
56 | final content = attributes['content'] ?? '';
57 | final isInline = attributes['isInline'] == 'true';
58 | final style = parentStyle ?? config.p.textStyle;
59 | if (content.isEmpty) return TextSpan(style: style, text: textContent);
60 | final latex = Math.tex(
61 | content,
62 | mathStyle: MathStyle.text,
63 | textScaleFactor: 1.5,
64 | onErrorFallback: (error) {
65 | return Text(
66 | textContent,
67 | style: style.copyWith(color: Colors.red),
68 | );
69 | },
70 | );
71 | return WidgetSpan(
72 | alignment: PlaceholderAlignment.middle,
73 | child: !isInline
74 | ? Container(
75 | width: double.infinity,
76 | margin: const EdgeInsets.symmetric(vertical: 16),
77 | child: SingleChildScrollView(
78 | scrollDirection: Axis.horizontal,
79 | child: latex,
80 | ),
81 | )
82 | : latex);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/note_tags.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:markdown_widget/markdown_widget.dart';
3 | import 'package:markdown/markdown.dart' as m;
4 |
5 | const _noteTag = 'noteTag';
6 |
7 | class NoteTagNode extends SpanNode {
8 | final Map attribute;
9 | final MarkdownConfig config;
10 | final Color? tagBackgroundColor;
11 | final Color? tagTextColor;
12 |
13 | String get noteTagId => attribute['id'] ?? '';
14 |
15 | NoteTagNode(this.attribute, this.config,
16 | {this.tagBackgroundColor, this.tagTextColor});
17 |
18 | @override
19 | InlineSpan build() {
20 | Color defaultBackgroundColor = const Color.fromRGBO(77, 142, 255, 0.298);
21 | Color defaultTextColor = const Color.fromRGBO(13, 71, 161, 1);
22 | return WidgetSpan(
23 | alignment: PlaceholderAlignment.middle,
24 | child: Container(
25 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
26 | margin: const EdgeInsets.symmetric(horizontal: 2),
27 | decoration: BoxDecoration(
28 | color: tagBackgroundColor ?? defaultBackgroundColor,
29 | borderRadius: BorderRadius.circular(8),
30 | ),
31 | child: Stack(
32 | children: [
33 | Text(
34 | noteTagId,
35 | style: config.p.textStyle.merge(parentStyle?.copyWith(
36 | foreground: Paint()
37 | ..style = PaintingStyle.stroke
38 | ..strokeWidth = 1.2
39 | ..color = tagTextColor ?? defaultTextColor,
40 | )),
41 | ),
42 | Text(
43 | noteTagId,
44 | style: config.p.textStyle.merge(parentStyle?.copyWith(
45 | color: tagTextColor ?? defaultTextColor,
46 | decoration: TextDecoration.underline,
47 | decorationColor: tagTextColor ?? defaultTextColor,
48 | )),
49 | ),
50 | ],
51 | )),
52 | );
53 | }
54 | }
55 |
56 | class NoteTagSyntax extends m.InlineSyntax {
57 | NoteTagSyntax() : super(r'#\w+');
58 |
59 | @override
60 | bool onMatch(m.InlineParser parser, Match match) {
61 | m.Element el = m.Element.withTag(_noteTag);
62 | final input = match.input.substring(match.start, match.end);
63 | el.attributes['id'] = input;
64 | parser.addNode(el);
65 | return true;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/rendering/underline.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:markdown_widget/markdown_widget.dart';
3 | import 'package:markdown/markdown.dart' as m;
4 |
5 | const _underlineTag = 'underline';
6 |
7 | class UnderlineNode extends SpanNode {
8 | final Map attribute;
9 | final MarkdownConfig config;
10 |
11 | String get underlineId => attribute['id'] ?? '';
12 |
13 | UnderlineNode(this.attribute, this.config);
14 |
15 | @override
16 | InlineSpan build() => TextSpan(
17 | text: underlineId,
18 | style: config.p.textStyle
19 | .merge(parentStyle?.copyWith(decoration: TextDecoration.underline)),
20 | );
21 | }
22 |
23 | SpanNodeGeneratorWithTag underlineGeneratorWithTag = SpanNodeGeneratorWithTag(
24 | tag: _underlineTag,
25 | generator: (e, config, visitor) => UnderlineNode(e.attributes, config));
26 |
27 | class UnderlineSyntax extends m.InlineSyntax {
28 | UnderlineSyntax() : super(r'__(.*?)__');
29 |
30 | @override
31 | bool onMatch(m.InlineParser parser, Match match) {
32 | m.Element el = m.Element.withTag(_underlineTag);
33 | final input = match.input;
34 | el.attributes['id'] = input.substring(match.start + 2, match.end - 2);
35 | parser.addNode(el);
36 | return true;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/toolbar/modal_input_url.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:printnotes/ui/components/markdown/toolbar/toolbar.dart';
3 |
4 | class ModalInputUrl extends StatelessWidget {
5 | const ModalInputUrl({
6 | super.key,
7 | required this.toolbar,
8 | required this.leftText,
9 | required this.selection,
10 | });
11 |
12 | final Toolbar toolbar;
13 | final String leftText;
14 | final TextSelection selection;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Container(
19 | margin: EdgeInsets.only(
20 | bottom: MediaQuery.viewInsetsOf(context).bottom,
21 | ),
22 | padding: const EdgeInsets.all(30),
23 | width: double.infinity,
24 | child: Column(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | mainAxisSize: MainAxisSize.min,
27 | children: [
28 | const Text(
29 | "Please provide a URL here.",
30 | style: TextStyle(
31 | fontSize: 18,
32 | fontWeight: FontWeight.bold,
33 | ),
34 | ),
35 | Container(
36 | margin: const EdgeInsets.only(top: 10, bottom: 15),
37 | padding: const EdgeInsets.symmetric(
38 | vertical: 15,
39 | horizontal: 15,
40 | ),
41 | decoration: BoxDecoration(
42 | border: Border.all(color: Colors.grey),
43 | borderRadius: BorderRadius.circular(10),
44 | ),
45 | child: TextField(
46 | autocorrect: false,
47 | autofocus: true,
48 | cursorRadius: const Radius.circular(20),
49 | decoration: const InputDecoration.collapsed(
50 | hintText: "Input your url.",
51 | ),
52 | style: const TextStyle(
53 | fontSize: 16,
54 | ),
55 | enableInteractiveSelection: true,
56 | onSubmitted: (String value) {
57 | Navigator.pop(context);
58 |
59 | /// check if the user entered an empty input
60 | if (value.isEmpty) {
61 | ScaffoldMessenger.of(context).showSnackBar(
62 | SnackBar(
63 | content: const Text(
64 | "Please input url",
65 | style: TextStyle(
66 | color: Colors.white,
67 | ),
68 | ),
69 | backgroundColor: Colors.red.withOpacity(0.8),
70 | duration: const Duration(milliseconds: 700),
71 | ),
72 | );
73 | } else {
74 | if (!value.contains(RegExp(r'https?:\/\/(www.)?([^\s]+)'))) {
75 | value = "http://$value";
76 | }
77 | toolbar.action(
78 | "$leftText$value)",
79 | "",
80 | textSelection: selection,
81 | );
82 | }
83 | },
84 | ),
85 | ),
86 | const Text(
87 | "example: https://example.com",
88 | style: TextStyle(
89 | fontSize: 12,
90 | color: Colors.grey,
91 | ),
92 | ),
93 | ],
94 | ),
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/ui/components/markdown/toolbar/toolbar_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:expandable/expandable.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:font_awesome_flutter/font_awesome_flutter.dart';
4 |
5 | class ToolbarItem extends StatelessWidget {
6 | const ToolbarItem({
7 | super.key,
8 | required this.icon,
9 | this.onPressedButton,
10 | this.tooltip,
11 | this.isExpandable = false,
12 | this.items,
13 | this.expandableBackground,
14 | });
15 |
16 | final dynamic icon;
17 | final VoidCallback? onPressedButton;
18 | final String? tooltip;
19 | final bool isExpandable;
20 | final List? items;
21 | final Color? expandableBackground;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return !isExpandable
26 | ? IconButton(
27 | onPressed: onPressedButton,
28 | splashColor: Theme.of(context).colorScheme.primary.withOpacity(0.4),
29 | highlightColor:
30 | Theme.of(context).colorScheme.primary.withOpacity(0.4),
31 | icon: icon is String
32 | ? Text(
33 | icon,
34 | style: const TextStyle(
35 | fontSize: 16,
36 | fontWeight: FontWeight.w900,
37 | ),
38 | )
39 | : Icon(
40 | icon,
41 | size: 20,
42 | ),
43 | tooltip: tooltip,
44 | )
45 | : ExpandableNotifier(
46 | child: Expandable(
47 | key: const Key("list_button"),
48 | collapsed: ExpandableButton(
49 | child: Padding(
50 | padding: const EdgeInsets.all(8.0),
51 | child: icon is String
52 | ? Text(
53 | icon,
54 | style: const TextStyle(
55 | fontSize: 14,
56 | fontWeight: FontWeight.w900,
57 | ),
58 | )
59 | : Icon(
60 | icon,
61 | size: 16,
62 | ),
63 | ),
64 | ),
65 | expanded: Container(
66 | color: expandableBackground ?? Colors.white,
67 | child: SingleChildScrollView(
68 | scrollDirection: Axis.horizontal,
69 | physics: const NeverScrollableScrollPhysics(),
70 | child: Row(
71 | children: [
72 | for (var item in items!) item,
73 | ExpandableButton(
74 | child: const Padding(
75 | padding: EdgeInsets.all(8.0),
76 | child: Icon(
77 | FontAwesomeIcons.solidCircleXmark,
78 | size: 16,
79 | color: Colors.red,
80 | ),
81 | ),
82 | ),
83 | ],
84 | ),
85 | ),
86 | ),
87 | ),
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/ui/screens/home/main_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:provider/provider.dart';
4 |
5 | import 'package:printnotes/providers/settings_provider.dart';
6 |
7 | import 'package:printnotes/ui/screens/home/main_scaffold.dart';
8 | import 'package:printnotes/ui/screens/home/intro_screen.dart';
9 | import 'package:printnotes/ui/screens/home/notes_display.dart';
10 | import 'package:printnotes/ui/components/drawer.dart';
11 | import 'package:printnotes/ui/components/dialogs/basic_popup.dart';
12 |
13 | class MainPage extends StatefulWidget {
14 | const MainPage({super.key, required this.title});
15 |
16 | final String title;
17 |
18 | @override
19 | State createState() => _MainPageState();
20 | }
21 |
22 | class _MainPageState extends State {
23 | bool _canPop = false;
24 |
25 | void _updateCanPop() => setState(() => _canPop = !_canPop);
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | final screenWidth = MediaQuery.sizeOf(context).width;
30 | const breakpoint = 1000.0;
31 | return context.watch().showIntro
32 | ? const IntroScreen()
33 | : PopScope(
34 | canPop: _canPop,
35 | onPopInvokedWithResult: (didPop, result) async {
36 | if (didPop) return;
37 |
38 | if (_canPop == true) {
39 | final exitPopup = await showBasicPopup(
40 | context, 'Exit App', 'Do you want to exit Print(Notes)?');
41 | if (exitPopup && context.mounted) {
42 | SystemNavigator.pop();
43 | }
44 | }
45 | },
46 | child: Row(
47 | children: [
48 | if (screenWidth >= breakpoint)
49 | SizedBox(
50 | width: 240,
51 | child: Drawer(
52 | backgroundColor: Theme.of(context).colorScheme.surface,
53 | shape: const RoundedRectangleBorder(
54 | borderRadius: BorderRadius.all(Radius.zero)),
55 | child: const DrawerView(),
56 | ),
57 | ),
58 | if (screenWidth >= breakpoint)
59 | Container(
60 | width: 0.5,
61 | color: Colors.black,
62 | ),
63 | Expanded(
64 | child: MainScaffold(
65 | title: widget.title,
66 | body: NotesDisplay(
67 | key: ValueKey(context.watch().mainDir),
68 | updateCanPop: _updateCanPop,
69 | ),
70 | drawer: screenWidth < breakpoint
71 | ? Drawer(
72 | backgroundColor:
73 | Theme.of(context).colorScheme.surface,
74 | child: const DrawerView(),
75 | )
76 | : null,
77 | ),
78 | ),
79 | ],
80 | ),
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/ui/screens/notes/image_viewer.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | import 'package:printnotes/utils/file_info.dart';
6 | import 'package:printnotes/utils/open_explorer.dart';
7 |
8 | class ImageViewScreen extends StatelessWidget {
9 | const ImageViewScreen({super.key, required this.imageFile});
10 |
11 | final File imageFile;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | double screenWidth = MediaQuery.sizeOf(context).width;
16 | bool isScreenLarge = screenWidth >= 600;
17 |
18 | return Scaffold(
19 | appBar: AppBar(
20 | centerTitle: true,
21 | title: SelectableText(
22 | imageFile.path.split('/').last,
23 | maxLines: 1,
24 | ),
25 | actions: [
26 | PopupMenuButton(
27 | onSelected: (value) {},
28 | itemBuilder: (context) => [
29 | PopupMenuItem(
30 | child: ListTile(
31 | leading: const Icon(Icons.folder_open),
32 | title: const Text("Open Location"),
33 | iconColor: mobileNullColor(context),
34 | textColor: mobileNullColor(context),
35 | ),
36 | onTap: () async =>
37 | await openExplorer(context, imageFile.parent.path),
38 | ),
39 | ],
40 | ),
41 | ],
42 | ),
43 | body: SingleChildScrollView(
44 | child: Container(
45 | margin: isScreenLarge
46 | ? EdgeInsets.symmetric(horizontal: (screenWidth - 600) / 2)
47 | : null,
48 | child: Column(
49 | children: [
50 | Image.file(imageFile),
51 | statListTile('File Size: ',
52 | getFileSizeString(bytes: imageFile.statSync().size)),
53 | statListTile('Last Modified: ',
54 | getFormattedDate(date: imageFile.statSync().modified)),
55 | statListTile('Location: ', imageFile.path),
56 | const SizedBox(height: 100)
57 | ],
58 | ),
59 | ),
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/ui/screens/settings/codeblock_theme_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:markdown_widget/widget/markdown_block.dart';
4 | import 'package:flutter_highlight/theme_map.dart';
5 |
6 | import 'package:printnotes/providers/theme_provider.dart';
7 | import 'package:printnotes/ui/components/markdown/build_markdown.dart';
8 |
9 | class CodeblockThemePage extends StatelessWidget {
10 | const CodeblockThemePage({super.key});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | List> codeHighlightThemes =
15 | themeMap.keys.map((key) {
16 | return DropdownMenuItem(value: key, child: Text(key));
17 | }).toList();
18 | String markdownData =
19 | '```Python\n@requires_authorization\ndef somefunc(param1=\'\', param2=0):\n r\'\'\'A docstring\'\'\'\n if param1 > param2: # interesting\n print \'Gre\\\'ater\'\n return (param2 - param1 + 1 + 0b10l) or None\n\nclass SomeClass:\n pass\n\n>>> message = \'\'\'interpreter\n... prompt\'\'\'\n```';
20 |
21 | return Scaffold(
22 | appBar: AppBar(
23 | centerTitle: true,
24 | title: Text('Select Code Theme'),
25 | ),
26 | body: Column(
27 | children: [
28 | ListTile(
29 | title: Text('Switch Theme Mode:'),
30 | trailing: Switch(
31 | value: Theme.of(context).brightness == Brightness.dark,
32 | onChanged: (value) {
33 | context
34 | .read()
35 | .setThemeMode(value ? 'dark' : 'light');
36 | }),
37 | ),
38 | ListTile(
39 | title: Text('Code Themes:'),
40 | trailing: DropdownButton(
41 | value: context.watch().codeHighlight,
42 | items: [
43 | DropdownMenuItem(value: '', child: Text('Auto - Default')),
44 | ...codeHighlightThemes
45 | ],
46 | onChanged: (value) {
47 | context.read().setCodeHighlight(value ?? '');
48 | },
49 | ),
50 | ),
51 | if (context.watch().codeHighlight == '')
52 | Text('Auto switches between a11y-dark and a11y-light'),
53 | MarkdownBlock(
54 | data: markdownData,
55 | selectable: false,
56 | config: theMarkdownConfigs(
57 | context,
58 | hideCodeButtons: true,
59 | inEditor: true,
60 | ),
61 | )
62 | ],
63 | ),
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/ui/widgets/custom_snackbar.dart:
--------------------------------------------------------------------------------
1 | import 'package:animated_snack_bar/animated_snack_bar.dart';
2 |
3 | AnimatedSnackBar customSnackBar(String text, {String? type, int? durationMil}) {
4 | // To avoid importing animated_snack_bar into every file
5 | AnimatedSnackBarType snackBarType;
6 | switch (type) {
7 | case "success":
8 | snackBarType = AnimatedSnackBarType.success;
9 | break;
10 | case "info":
11 | snackBarType = AnimatedSnackBarType.info;
12 | break;
13 | case "warning":
14 | snackBarType = AnimatedSnackBarType.warning;
15 | break;
16 | case "error":
17 | snackBarType = AnimatedSnackBarType.error;
18 | break;
19 | default:
20 | snackBarType = AnimatedSnackBarType.info;
21 | }
22 | return AnimatedSnackBar.material(
23 | text,
24 | type: snackBarType,
25 | mobileSnackBarPosition: MobileSnackBarPosition.bottom,
26 | desktopSnackBarPosition: DesktopSnackBarPosition.bottomRight,
27 | duration: Duration(milliseconds: durationMil ?? 2000),
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/lib/ui/widgets/file_info_bottom_sheet.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:printnotes/utils/file_info.dart';
5 |
6 | Future modalShowFileInfo(BuildContext context, String filePath) =>
7 | showModalBottomSheet(
8 | context: context,
9 | useSafeArea: true,
10 | isScrollControlled: true,
11 | builder: (context) {
12 | File file = File(filePath);
13 | return Column(
14 | mainAxisSize: MainAxisSize.min,
15 | children: [
16 | const Padding(
17 | padding: EdgeInsets.only(top: 20),
18 | child: Text(
19 | 'Info',
20 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
21 | textAlign: TextAlign.center,
22 | ),
23 | ),
24 | statListTile('Character Count:', getCharacterCount(filePath)),
25 | statListTile('Word Count:', getWordCount(filePath)),
26 | statListTile(
27 | 'File Size: ', getFileSizeString(bytes: file.statSync().size)),
28 | statListTile('Last Modified: ',
29 | getFormattedDate(date: file.statSync().modified)),
30 | statListTile('Location: ', file.path),
31 | const SizedBox(height: 50)
32 | ],
33 | );
34 | },
35 | );
36 |
--------------------------------------------------------------------------------
/lib/ui/widgets/list_section_title.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | Widget sectionTitle(String title, Color color,
4 | {double? padding, double? fontSize}) {
5 | return Padding(
6 | padding: EdgeInsets.all(padding ?? 8),
7 | child: Text(
8 | title,
9 | style: TextStyle(
10 | fontSize: fontSize ?? 20,
11 | color: color,
12 | fontWeight: FontWeight.bold,
13 | ),
14 | ),
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/lib/ui/widgets/speed_dial_fab.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 | import 'package:flutter_speed_dial/flutter_speed_dial.dart';
4 |
5 | import 'package:printnotes/providers/navigation_provider.dart';
6 | import 'package:printnotes/utils/handlers/item_create.dart';
7 |
8 | Widget speedDialFAB(
9 | BuildContext context, String currentFolder, Function loadItems) {
10 | return SpeedDial(
11 | icon: Icons.add,
12 | activeIcon: Icons.close,
13 | childPadding: const EdgeInsets.all(5),
14 | spaceBetweenChildren: 10,
15 | children: [
16 | SpeedDialChild(
17 | child: const Icon(Icons.create_new_folder_outlined),
18 | label: 'Create Folder',
19 | onTap: () => ItemCreationHandler.handleCreateNewFolder(
20 | context, currentFolder, loadItems),
21 | ),
22 | SpeedDialChild(
23 | child: const Icon(Icons.note_add_outlined),
24 | label: 'Create Note',
25 | onTap: () => ItemCreationHandler.handleCreateNewNote(
26 | context, currentFolder, loadItems),
27 | ),
28 | SpeedDialChild(
29 | child: const Icon(Icons.folder_copy),
30 | label: 'Open External File',
31 | onTap: () =>
32 | context.read().openExternalFile(context),
33 | )
34 | ],
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/lib/utils/config_file/custom_themes/theme_validator.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | import 'package:printnotes/utils/configs/data_path.dart';
6 |
7 | bool validateCustomThemeName(String name) {
8 | try {
9 | final configFile = DataPath.loadJsonConfigFile();
10 | if (configFile['UserCustomThemes'] != null) {
11 | final themes = configFile['UserCustomThemes'] as List;
12 | for (final theme in themes) {
13 | if (theme['name'] == name) return false;
14 | }
15 | }
16 |
17 | return true;
18 | } catch (e) {
19 | debugPrint('Error parsing JSON in validator for Custom Theme Name: $e');
20 | return false;
21 | }
22 | }
23 |
24 | bool validateCustomThemeJson(String jsonString) {
25 | try {
26 | // Parse string into json
27 | final Map jsonMap = jsonDecode(jsonString);
28 |
29 | // Required keys/colors and their expected value type (all should be int)
30 | final requiredKeys = [
31 | 'brightness',
32 | 'primary',
33 | 'onPrimary',
34 | 'secondary',
35 | 'onSecondary',
36 | 'surface',
37 | 'onSurface',
38 | 'surfaceContainer'
39 | ];
40 |
41 | // Check if all required colors exist and are integers
42 | for (final key in requiredKeys) {
43 | if (!jsonMap.containsKey(key)) {
44 | debugPrint('Missing required key: $key');
45 | return false;
46 | }
47 |
48 | if (jsonMap[key] is! int) {
49 | debugPrint('Value for key $key is not an integer');
50 | return false;
51 | }
52 | // Check if brightness is either 0 (dark) or 1 (light)
53 | if (key == 'brightness' && jsonMap[key] != 0 && jsonMap[key] != 1) {
54 | debugPrint('Brightness invalid value');
55 | return false;
56 | }
57 |
58 | // Check if color value is within 64-bits
59 | if (!key.contains('brightness') && jsonMap[key] > 9223372036854775807 ||
60 | jsonMap[key] < -9223372036854775808) {
61 | debugPrint('Color integer exceeds 64-bits');
62 | return false;
63 | }
64 | }
65 |
66 | // Check if there are any extra keys
67 | if (jsonMap.length != requiredKeys.length) {
68 | final extraKeys =
69 | jsonMap.keys.where((key) => !requiredKeys.contains(key));
70 | debugPrint('Extra unexpected keys found: $extraKeys');
71 | return false;
72 | }
73 |
74 | return true;
75 | } catch (e) {
76 | debugPrint('Error parsing JSON in validator for Custom Theme Parser: $e');
77 | return false;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/utils/config_file/toolbar_config_handler.dart:
--------------------------------------------------------------------------------
1 | import 'package:printnotes/utils/configs/data_path.dart';
2 |
3 | class ToolbarConfigItem {
4 | final String key;
5 | bool visible;
6 |
7 | ToolbarConfigItem({
8 | required this.key,
9 | required this.visible,
10 | });
11 |
12 | factory ToolbarConfigItem.fromJson(Map json) {
13 | return ToolbarConfigItem(
14 | key: json['key'],
15 | visible: json['visible'],
16 | );
17 | }
18 |
19 | Map toJson() => {
20 | 'key': key,
21 | 'visible': visible,
22 | };
23 | }
24 |
25 | void saveToolbarLoadout(List toolbarConfigList) {
26 | final Map configFileMap = DataPath.loadJsonConfigFile();
27 |
28 | // Object existence check for "toolbarConfig", create if null
29 | configFileMap['toolbarConfig'] ??= [];
30 |
31 | var jsonList = toolbarConfigList.map((e) => e.toJson()).toList();
32 |
33 | // Add to toolbar configurations to json
34 | configFileMap['toolbarConfig'] = jsonList;
35 |
36 | DataPath.saveJsonConfigFile(configFileMap);
37 | }
38 |
39 | List? loadToolbarLoadout() {
40 | final Map configFileMap = DataPath.loadJsonConfigFile();
41 | if (configFileMap['toolbarConfig'] != null) {
42 | return (configFileMap['toolbarConfig'] as List)
43 | .map((e) => ToolbarConfigItem.fromJson(e))
44 | .toList();
45 | }
46 | return null;
47 | }
48 |
--------------------------------------------------------------------------------
/lib/utils/configs/data_path.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:convert';
3 |
4 | import 'package:file_picker/file_picker.dart';
5 | import 'package:path_provider/path_provider.dart';
6 | import 'package:permission_handler/permission_handler.dart';
7 | import 'package:shared_preferences/shared_preferences.dart';
8 |
9 | class DataPath {
10 | static String? _selectedDirectory;
11 | static const String _prefKey = 'selected_directory';
12 |
13 | static Future pickDirectory() async {
14 | if (Platform.isIOS) {
15 | final appDir = await getApplicationDocumentsDirectory();
16 | return appDir.path;
17 | }
18 | String? selectedDirectory = await FilePicker.platform.getDirectoryPath();
19 | if (Platform.isAndroid) {
20 | final status = await Permission.manageExternalStorage.request();
21 | if (!status.isGranted) {
22 | throw "Please allow storage permission to access files";
23 | }
24 | }
25 | if (selectedDirectory != null) {
26 | await setSelectedDirectory(selectedDirectory);
27 | }
28 | return selectedDirectory;
29 | }
30 |
31 | static Future setSelectedDirectory(String dirPath) async {
32 | _selectedDirectory = dirPath;
33 | final dir = Directory(_selectedDirectory!);
34 | if (!await dir.exists()) {
35 | await dir.create(recursive: true);
36 | }
37 | final prefs = await SharedPreferences.getInstance();
38 | await prefs.setString(_prefKey, dirPath);
39 | }
40 |
41 | static Future get selectedDirectory async {
42 | if (_selectedDirectory == null) {
43 | final prefs = await SharedPreferences.getInstance();
44 | _selectedDirectory = prefs.getString(_prefKey);
45 | if (_selectedDirectory == null) {
46 | final appDir = await getApplicationDocumentsDirectory();
47 | return _selectedDirectory = appDir.path;
48 | }
49 | final dir = Directory(_selectedDirectory!);
50 | if (!await dir.exists()) {
51 | await dir.create(recursive: true);
52 | }
53 | }
54 | return _selectedDirectory;
55 | }
56 |
57 | // Hidden app config file called .main_config.json
58 |
59 | // Create and load contents of config file
60 | static final configFile = File(
61 | '$_selectedDirectory${Platform.pathSeparator}.printnotes${Platform.pathSeparator}main_config.json');
62 |
63 | static Map loadJsonConfigFile() {
64 | if (!configFile.existsSync()) configFile.createSync(recursive: true);
65 | if (configFile.readAsStringSync().isEmpty) {
66 | configFile.writeAsStringSync('{}');
67 | }
68 |
69 | final configJsonString = configFile.readAsStringSync();
70 | return jsonDecode(configJsonString);
71 | }
72 |
73 | // Write to config file
74 | static void saveJsonConfigFile(Map configData) async {
75 | final configJsonString =
76 | const JsonEncoder.withIndent(' ').convert(configData);
77 | configFile.writeAsStringSync(configJsonString);
78 | }
79 |
80 | // Deletes and regenerates json file
81 | static void deleteJsonConfigFile() async {
82 | configFile.delete().then((_) => loadJsonConfigFile());
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/utils/configs/user_intro.dart:
--------------------------------------------------------------------------------
1 | import 'package:shared_preferences/shared_preferences.dart';
2 |
3 | class UserFirstTime {
4 | static Future setShowIntro(bool showIntro) async {
5 | final prefs = await SharedPreferences.getInstance();
6 | await prefs.setBool('firstTimeUser', showIntro);
7 | }
8 |
9 | static Future get getShowIntro async {
10 | final prefs = await SharedPreferences.getInstance();
11 | return prefs.getBool('firstTimeUser') ?? true;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/utils/file_info.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'dart:math';
3 | import 'package:intl/intl.dart';
4 |
5 | import 'package:flutter/material.dart';
6 |
7 | String getCharacterCount(String filePath) {
8 | File file = File(filePath);
9 | String fileString = file.readAsStringSync();
10 | return fileString.length.toString();
11 | }
12 |
13 | String getWordCount(String filePath) {
14 | File file = File(filePath);
15 | String fileString = file.readAsStringSync();
16 | int wordCount =
17 | RegExp(r'[\w._]+').allMatches(fileString.replaceAll('\n', ' ')).length;
18 | return wordCount.toString();
19 | }
20 |
21 | String getFileSizeString({required int bytes, int decimals = 0}) {
22 | const suffixes = [" B", " KiB", " MiB", " GiB", " TiB"];
23 | if (bytes == 0) return '0${suffixes[0]}';
24 | var i = (log(bytes) / log(1024)).floor();
25 | return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + suffixes[i];
26 | }
27 |
28 | String getFormattedDate({required DateTime date}) {
29 | return DateFormat.yMMMd().add_jm().format(date);
30 | }
31 |
32 | Widget statListTile(titleText, subtitleText) {
33 | return ListTile(
34 | title: SelectableText(
35 | titleText,
36 | style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
37 | maxLines: 1,
38 | ),
39 | subtitle: SelectableText(
40 | subtitleText,
41 | style: const TextStyle(fontSize: 20, overflow: TextOverflow.ellipsis),
42 | minLines: 1,
43 | maxLines: 3,
44 | ),
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/lib/utils/handlers/file_extensions.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:printnotes/constants/constants.dart';
3 |
4 | enum CFileType {
5 | note,
6 | image,
7 | pdf,
8 | unknown,
9 | }
10 |
11 | CFileType fileTypeChecker(FileSystemEntity file) {
12 | if (allowedNoteExtensions.any((ext) => file.path.endsWith(ext))) {
13 | return CFileType.note;
14 | } else if (allowedImageExtensions.any((ext) => file.path.endsWith(ext))) {
15 | return CFileType.image;
16 | } else if (allowedPdfExtensions.any((ext) => file.path.endsWith(ext))) {
17 | return CFileType.pdf;
18 | } else {
19 | return CFileType.unknown;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/utils/handlers/frontmatter_parser.dart:
--------------------------------------------------------------------------------
1 | import 'package:cosmic_frontmatter/cosmic_frontmatter.dart';
2 |
3 | class FrontmatterHandleParsing {
4 | /// Pass markdown data and get access to frontmatter tags
5 | ///
6 | /// handler.getParsedData(fileAsString).frontmatter['title'] // -> 'markdown title' or null
7 | ///
8 | /// or just get plain body without the frontmatter syntax
9 | ///
10 | /// handler.getParsedData(fileAsString).body
11 | static Document? getParsedData(String content) {
12 | Document? doc;
13 | try {
14 | doc = parseFrontmatter(
15 | content: content,
16 | frontmatterBuilder: (map) => map,
17 | );
18 | } catch (e) {
19 | doc = null;
20 | }
21 | return doc;
22 | }
23 |
24 | /// Get just the data from the a tag to avoid having too many
25 | /// null checks from parsed [Document] and desired tag when
26 | /// only using [getParsedData]
27 | ///
28 | /// f.getTagString(fileAsString, 'title') // -> 'markdown title' or null
29 | static String? getTagString(String content, String tag) {
30 | String? tagData;
31 | final Document? doc = getParsedData(content);
32 |
33 | try {
34 | if (doc != null) tagData = doc.frontmatter[tag];
35 | } catch (e) {
36 | tagData = null;
37 | }
38 | return tagData;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/utils/handlers/item_archive.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:flutter/material.dart';
3 | import 'package:printnotes/utils/storage_system.dart';
4 | import 'package:printnotes/ui/widgets/custom_snackbar.dart';
5 |
6 | class ItemArchiveHandler {
7 | static Future handleArchiveItem(
8 | BuildContext context, FileSystemEntity item, Function loadItems) async {
9 | showDialog(
10 | context: context,
11 | builder: (context) => AlertDialog(
12 | title: const Text('Confirm Archive'),
13 | content: Text(
14 | 'Are you sure you want to archive this ${item is Directory ? 'folder' : 'file'}? It will be removed from its current location.'),
15 | actions: [
16 | TextButton(
17 | child: Text(
18 | 'Cancel',
19 | style: TextStyle(color: Theme.of(context).colorScheme.secondary),
20 | ),
21 | onPressed: () => Navigator.of(context).pop(),
22 | ),
23 | TextButton(
24 | child: Text(
25 | 'Archive',
26 | style: TextStyle(color: Theme.of(context).colorScheme.secondary),
27 | ),
28 | onPressed: () async {
29 | Navigator.of(context).pop();
30 | try {
31 | await StorageSystem.archiveItem(item.path);
32 | if (context.mounted) {
33 | customSnackBar(
34 | '${item is Directory ? 'Folder' : 'File'} archived successfully',
35 | type: 'success')
36 | .show(context);
37 | }
38 | loadItems();
39 | } catch (e) {
40 | if (context.mounted) {
41 | customSnackBar('Error archiving item: $e', type: 'error')
42 | .show(context);
43 | }
44 | }
45 | },
46 | ),
47 | ],
48 | ),
49 | );
50 | }
51 |
52 | static Future handleUnarchiveItem(
53 | BuildContext context, FileSystemEntity item, Function loadItems) async {
54 | try {
55 | await StorageSystem.unarchiveItem(item.path);
56 | if (context.mounted) {
57 | customSnackBar('Item unarchived successfully', type: 'success')
58 | .show(context);
59 | }
60 | loadItems();
61 | } catch (e) {
62 | if (context.mounted) {
63 | customSnackBar('Error unarchiving item: $e', type: 'error')
64 | .show(context);
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/utils/handlers/item_create.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:path/path.dart' as path;
5 | import 'package:provider/provider.dart';
6 |
7 | import 'package:printnotes/providers/navigation_provider.dart';
8 | import 'package:printnotes/utils/configs/data_path.dart';
9 | import 'package:printnotes/utils/storage_system.dart';
10 | import 'package:printnotes/ui/widgets/custom_snackbar.dart';
11 |
12 | class ItemCreationHandler {
13 | static Future handleCreateNewFolder(
14 | BuildContext context,
15 | String currentPath,
16 | Function loadItems,
17 | ) async {
18 | final dialogResult =
19 | await showNameInputDialog(context, 'Enter folder name');
20 | String folderName = dialogResult['name'];
21 | bool folderSubmitted = dialogResult['submitted'];
22 | if (folderSubmitted == true && folderName.isNotEmpty) {
23 | try {
24 | final newFolderPath = await StorageSystem.createFolder(folderName,
25 | parentPath: currentPath);
26 | if (context.mounted) {
27 | customSnackBar(
28 | 'Folder created: ${path.relative(newFolderPath, from: await DataPath.selectedDirectory)}',
29 | type: 'success')
30 | .show(context);
31 | }
32 | loadItems();
33 | } catch (e) {
34 | if (context.mounted) {
35 | customSnackBar('Error creating folder: $e', type: 'error')
36 | .show(context);
37 | }
38 | }
39 | }
40 | }
41 |
42 | static Future handleCreateNewNote(
43 | BuildContext context,
44 | String currentPath,
45 | Function loadItems,
46 | ) async {
47 | final dialogResult = await showNameInputDialog(context, 'Enter note name');
48 | String noteName = dialogResult['name'];
49 | bool noteSubmitted = dialogResult['submitted'];
50 | if (noteSubmitted && noteName.isNotEmpty) {
51 | try {
52 | await StorageSystem.createFile(noteName, parentPath: currentPath)
53 | .then((e) {
54 | if (context.mounted) {
55 | context
56 | .read()
57 | .routeItemToPage(context, File(e));
58 | }
59 | });
60 |
61 | loadItems();
62 | } catch (e) {
63 | if (context.mounted) {
64 | customSnackBar('Error creating note: $e', type: 'error')
65 | .show(context);
66 | }
67 | }
68 | }
69 | }
70 |
71 | static Future