Merge branch 'master' into fix-flutter-3.16

pull/1489/head
Ellet 1 year ago committed by GitHub
commit d1a06a11aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 118
      .github/ISSUE_TEMPLATE/1_bug.yml
  2. 41
      .github/ISSUE_TEMPLATE/2_feature_request.yml
  3. 24
      .github/ISSUE_TEMPLATE/3_question.yml
  4. 25
      .github/ISSUE_TEMPLATE/issue-template.md
  5. 48
      .github/PULL_REQUEST_TEMPLATE.md
  6. 25
      .github/workflows/main.yml
  7. 38
      .github/workflows/publish.yml
  8. 18
      .github/workflows/welcome.yml
  9. 6
      .gitignore
  10. 13
      .pubignore
  11. 1580
      CHANGELOG.md
  12. 64
      CONTRIBUTING.md
  13. 0
      FETCH_HEAD
  14. 2
      LICENSE
  15. 699
      README.md
  16. 59
      analysis_options.yaml
  17. 0
      doc/code_introduction.md
  18. 43
      doc/configurations/custom_buttons.md
  19. 15
      doc/configurations/font_size.md
  20. 25
      doc/configurations/localizations_setup.md
  21. 34
      doc/configurations/using_custom_app_widget.md
  22. 124
      doc/custom_embed_blocks.md
  23. 143
      doc/custom_toolbar.md
  24. 3
      doc/development_notes.md
  25. 6
      doc/migration.md
  26. 2
      doc/readme/cn.md
  27. 48
      doc/todo.md
  28. 65
      doc/translation.md
  29. 10
      example/.gitignore
  30. 39
      example/.metadata
  31. 22
      example/README.md
  32. 37
      example/analysis_options.yaml
  33. 33
      example/android/app/build.gradle
  34. 7
      example/android/app/src/debug/AndroidManifest.xml
  35. 40
      example/android/app/src/main/AndroidManifest.xml
  36. 5
      example/android/app/src/main/kotlin/com/example/example/MainActivity.kt
  37. 2
      example/android/app/src/main/res/values-night/styles.xml
  38. 4
      example/android/app/src/main/res/values/strings.xml
  39. 2
      example/android/app/src/main/res/values/styles.xml
  40. 7
      example/android/app/src/profile/AndroidManifest.xml
  41. 47
      example/android/build.gradle
  42. 3
      example/android/gradle.properties
  43. 3
      example/android/gradle/wrapper/gradle-wrapper.properties
  44. 25
      example/android/settings.gradle
  45. BIN
      example/assets/images/screenshot_1.png
  46. BIN
      example/assets/images/screenshot_2.png
  47. BIN
      example/assets/images/screenshot_3.png
  48. BIN
      example/assets/images/screenshot_4.png
  49. 540
      example/assets/sample_data.json
  50. 521
      example/assets/sample_data_nomedia.json
  51. 2
      example/ios/.gitignore
  52. 2
      example/ios/Flutter/AppFrameworkInfo.plist
  53. 3
      example/ios/Podfile
  54. 261
      example/ios/Runner.xcodeproj/project.pbxproj
  55. 19
      example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  56. 3
      example/ios/Runner.xcworkspace/contents.xcworkspacedata
  57. 6
      example/ios/Runner/AppDelegate.h
  58. 13
      example/ios/Runner/AppDelegate.m
  59. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  60. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  61. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  62. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  63. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  64. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  65. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  66. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  67. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  68. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  69. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  70. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  71. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  72. BIN
      example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  73. 16
      example/ios/Runner/Info.plist
  74. 9
      example/ios/Runner/main.m
  75. 12
      example/ios/RunnerTests/RunnerTests.swift
  76. 114
      example/lib/gen/assets.gen.dart
  77. 39
      example/lib/gen/fonts.gen.dart
  78. 1
      example/lib/logic/empty.dart
  79. 152
      example/lib/main.dart
  80. 564
      example/lib/pages/home_page.dart
  81. 81
      example/lib/pages/read_only_page.dart
  82. 13
      example/lib/presentation/extensions/scaffold_messenger.dart
  83. 53
      example/lib/presentation/home/widgets/example_item.dart
  84. 188
      example/lib/presentation/home/widgets/home_screen.dart
  85. 9
      example/lib/presentation/quill/embeds/timestamp_embed.dart
  86. 138
      example/lib/presentation/quill/quill_editor.dart
  87. 144
      example/lib/presentation/quill/quill_screen.dart
  88. 301
      example/lib/presentation/quill/quill_toolbar.dart
  89. 295
      example/lib/presentation/quill/samples/quill_default_sample.dart
  90. 72
      example/lib/presentation/quill/samples/quill_images_sample.dart
  91. 270
      example/lib/presentation/quill/samples/quill_text_sample.dart
  92. 19
      example/lib/presentation/quill/samples/quill_videos_sample.dart
  93. 26
      example/lib/presentation/settings/cubit/settings_cubit.dart
  94. 202
      example/lib/presentation/settings/cubit/settings_cubit.freezed.dart
  95. 40
      example/lib/presentation/settings/cubit/settings_cubit.g.dart
  96. 22
      example/lib/presentation/settings/cubit/settings_state.dart
  97. 122
      example/lib/presentation/settings/widgets/settings_screen.dart
  98. 63
      example/lib/presentation/shared/widgets/dialog_action.dart
  99. 24
      example/lib/presentation/shared/widgets/home_screen_button.dart
  100. 3
      example/lib/universal_ui/fake_ui.dart
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,118 @@
name: Report a bug
description: |
You found a bug in Flutter Quill causing your application to crash or
throw an exception, a widget is buggy, unexpected behavior or something looks wrong.
labels: 'bug'
body:
- type: markdown
attributes:
value: |
Thank you for using Flutter Quill!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
options:
- label: I have searched the [existing issues](https://github.com/singerdmx/flutter-quill/issues)
required: true
- type: input
attributes:
label: Flutter Quill version
description: Please tell us which version of `flutter_quill` that you are using.
placeholder: For example 9.0.0
validations:
required: true
- type: textarea
attributes:
label: Other Flutter Quill packages versions
description: If you are using any other packages like `flutter_quill_extensions` or `flutter_quill_test` please mention the versions here
placeholder: |
flutter_quill_extensions: ^0.6.10
flutter_quill_test: ^0.0.5
validations:
required: false
- type: textarea
attributes:
label: Steps to reproduce
description: Please tell us exactly how to reproduce the problem you are running into.
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
attributes:
label: Expected results
description: Please tell us what is expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual results
description: Please tell us what is actually happening.
validations:
required: true
- type: textarea
attributes:
label: Code sample
description: |
Please create a minimal reproducible sample that shows the problem
and attach it below between the lines with the backticks.
To create it, use the `flutter create bug` command and update the `main.dart` file.
Alternatively, you can use https://dartpad.dev/ or create a public GitHub
repository to share your sample.
Note: Please do not upload screenshots of text. Instead, use code blocks
or the above mentioned ways to upload your code sample.
value: |
<details><summary>Code sample</summary>
```dart
[Paste your code here]
```
</details>
validations:
required: true
- type: textarea
attributes:
label: Screenshots or Video
description: |
Upload any screenshots or video of the bug if applicable.
value: |
<details>
<summary>Screenshots / Video demonstration</summary>
[Upload media here]
</details>
validations:
required: false
- type: textarea
attributes:
label: Logs
description: |
Include the full logs of the commands you are running between the lines
with the backticks below. If you are running any `flutter` commands,
please include the output of running them with `--verbose`; for example,
the output of running `flutter --verbose create foo`.
If the logs are too large to be uploaded to GitHub, you may upload
them as a `txt` file or use online tools like https://pastebin.com to
share it.
Note: Please do not upload screenshots of text. Instead, use code blocks
or the above mentioned ways to upload logs.
value: |
<details><summary>Logs</summary>
```console
[Paste your logs here]
```
</details>
validations:
required: false

@ -0,0 +1,41 @@
name: Feature request
description: Suggest a new idea for Flutter Quill.
labels: 'enhancement'
body:
- type: markdown
attributes:
value: |
Thank you for using Flutter Quill!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for this feature request or proposal.
options:
- label: I have searched the [existing issues](https://github.com/singerdmx/flutter-quill/issues)
required: true
- type: textarea
attributes:
label: Use case
description: |
Please tell us the problem you are running into that led to you wanting
a new feature.
Is your feature request related to a problem? Please give a clear and
concise description of what the problem is.
Describe the alternative solutions you've considered. Is there already a solution that solves this?
validations:
required: true
- type: textarea
attributes:
label: Proposal
description: |
Briefly but precisely describe what you would like Flutter Quill to be able to do.
Consider attaching something showing what you are imagining:
* images
* videos
* code samples
validations:
required: true

@ -0,0 +1,24 @@
name: Ask a question
description: |
If you have any questions, feel free to ask
labels: 'help wanted'
body:
- type: markdown
attributes:
value: |
Thank you for using Flutter Quill!
- type: checkboxes
attributes:
label: Is there an existing issue for this?
options:
- label: I have searched the [existing issues](https://github.com/singerdmx/flutter-quill/issues)
required: true
- type: textarea
attributes:
label: The question
description: Please explain your question here
placeholder: |
How do I save the images of Quill Editor?
validations:
required: true

@ -1,25 +0,0 @@
---
name: Issue template
about: Common things to fill
title: "[Web] or [Mobile] or [Desktop]"
labels: ''
assignees: ''
---
My issue is about [Web]
My issue is about [Mobile]
My issue is about [Desktop]
I have tried running `example` directory successfully before creating an issue here.
Please note that we are using <b>latest</b> flutter version in stable channel on branch master. If you are using beta or master channel, or you are not using <b>latest</b> flutter version in stable channel, you may experience error.
<!-- Please explain how to encounter the issue in details if possible -->
<!-- Don't forgot to mention the platform you are testing in -->
<!-- Insert your images here if possible -->
<!-- Images: -->
<!-- Add short video that showcase the problem will help -->

@ -1,30 +1,20 @@
# Pull Request
## Description
Provide a brief description of your changes.
*Replace this paragraph with a description of what this PR is doing. If you're modifying existing behavior, describe the existing behavior, how this PR is changing it, and what motivated the change.*
## Issues
## Related Issues
<!-- Remove this if your pull request address changes other than existing issues -->
Closes #IssueNumber
(Replace "IssueNumber" with the actual issue number you are addressing.)
*Replace this paragraph with a list of issues related to this PR from the [issue database](https://github.com/singerdmx/flutter-quill/issues). Indicate, which of these issues are resolved or fixed by this PR.*
## Improvements
<!-- Please tell us the improvements you made in a list -->
*e.g.*
- *Fix #123*
- *Related #456*
<!-- Example: -->
- Improve code readability
- Improve performance
## Improvements
<!-- Optional -->
## Features
<!-- Please tell us the features you added in a list if you add any -->
<!-- Example: -->
- Add a new feature
- Allow to customize the widgets
<!-- Remove this if your pull request about other changes -->
<!-- Optional -->
## Additional notes
<!-- Optional -->
@ -34,13 +24,15 @@ Closes #IssueNumber
## Checklist
<!-- Mark all that applies with `[x]` -->
- [ ] I read the [Contributor Guide](../CONTRIBUTING.md) and followed the process outlined there for submitting PRs.
- [ ] I titled the PR using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0).
- [ ] I did not modify the `CHANGELOG.md` nor the plugin version in `pubspec.yaml` files.
- [ ] All existing and new tests are passing.
- [ ] I have run the commands in `./scripts/before-push.sh` and it all passed successfully
## Breaking Change
Does your PR require plugin users to manually update their apps to accommodate your change?
- [ ] I have added/updated relevant documentation <!-- REQUIRED -->
- [ ] I have tested these changes locally. <!-- REQUIRED -->
- [ ] I have followed the code style and guidelines. <!-- REQUIRED -->
- [ ] I have updated `CHANGELOG.md` with my changes in the next section <!-- REQUIRED -->
- [ ] I have run `dart format .`` on the project <!-- REQUIRED -->
- [ ] I have run `dart fix --apply` on the project <!-- REQUIRED -->
- [ ] I have run `flutter test` and `flutter analyze` and it passed successfully <!-- REQUIRED -->
- [ ] I have run `./before-push.sh` and everything is fine <!-- Optional -->
- [ ] Yes, this is a breaking change (please indicate that with a `!` in the title as explained in [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0)).
- [ ] No, this is *not* a breaking change.

@ -1,4 +1,4 @@
name: flutter-quill CI
name: Flutter Quill CI
on:
push:
@ -11,10 +11,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
- name: Check flutter version
run: flutter --version
@ -25,14 +26,34 @@ jobs:
- name: Install flutter_quill_extensions dependencies
run: flutter pub get -C flutter_quill_extensions
- name: Install flutter_quill_test dependencies
run: flutter pub get -C flutter_quill_test
- name: Install quill_html_converter dependencies
run: flutter pub get -C packages/quill_html_converter
- name: Run flutter analysis
run: flutter analyze
- name: Check dart code formatting
run: dart format --set-exit-if-changed .
- name: Preview dart proposed changes
run: dart fix --dry-run
- name: Check if package is ready for publishing
run: flutter pub publish --dry-run
- name: Run flutter tests
run: flutter test
- name: Flutter build Web
run: flutter build web --release --verbose --dart-define=CI=true
working-directory: ./example
# - name: Install flutter Linux prerequisites
# run: sudo apt-get install clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev -y
# - name: Flutter build Linux
# run: flutter build linux --release --verbose --dart-define=CI=true
# working-directory: ./example

@ -0,0 +1,38 @@
name: Publish to pub.dev
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
publish:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Check flutter version
run: flutter --version
# This is needed in order for the authentication to success
# pub token add https://pub.dev --env-var PUB_TOKEN
# Requests to "https://pub.dev" will now be authenticated using the secret token stored in the environment variable "PUB_TOKEN".
- uses: dart-lang/setup-dart@v1
- name: Install dependencies
run: flutter pub get
# Here you can insert custom steps you need
# - run: dart tool/generate-code.dart
- name: Re-generate the translations
run: ./scripts/regenerate-translations.sh
- name: Publish
run: flutter pub publish --force

@ -0,0 +1,18 @@
name: 'Welcome New Contributors'
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
jobs:
welcome-new-contributor:
runs-on: ubuntu-latest
steps:
- name: 'Greet the contributor'
uses: garg3133/welcome-new-contributors@v1.2
with:
token: ${{ secrets.BOT_ACCESS_TOKEN }}
issue-message: 'Hello there, on behalf the Flutter Quill Team I would like to thank you for opening your first issue. Your inputs and insights are valuable in shaping a stable and reliable version for all our users. Thank you for being part of the open-source community!'
pr-message: 'Hi there, thanks for opening your first Pull Request to this project!!'

6
.gitignore vendored

@ -4,6 +4,7 @@
*.pyc
*.swp
.DS_Store
**/.DS_Store
.atom/
.buildlog/
.history
@ -75,3 +76,8 @@ example/ios/Podfile.lock
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
pubspec.lock
# For local development
pubspec_overrides.yaml
old_example

@ -0,0 +1,13 @@
# For local development
pubspec_overrides.yaml
pubspec_overrides.yaml.disabled
# Github
.github/
# The example
example/.fvm/
example/build/
example/.dart_tool/
scripts/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,64 @@
# Contributing
The contributions are more than welcome! <br>
This project will be better with the open-source community help
You can check the [Todo](./doc/todo.md) list if you want to
There are no guidelines for now.
This page will be updated in the future.
## Steps to contributing
You will need a GitHub account as well as Git installed and configured with your GitHub account on your machine
1. Fork the repository in GitHub
2. clone the forked repository using `git`
3. Add the `upstream` repository using:
```
git remote add upstream git@github.com:singerdmx/flutter-quill.git
```
4. Open the project with your favorite IDE, usually, we prefer to use Jetbrains IDEs, but since [VS Code](https://code.visualstudio.com) is more used and has more support for Dart, then we suggest using it if you want to.
5. Create a new git branch and switch to it using:
```
git checkout -b your-branch-name
```
The `your-branch-name` is your choice
6. Make your changes
7. If you are working on changes that depend on different libraries in the same repo, then in that directory copy `pubspec_overrides.yaml.disabled` which exists in all the libraries (`flutter_quill_test` and `flutter_quill_extensions` etc...)
to `pubspec_overrides.yaml` which will be ignored by `.gitignore` and will be used by dart pub to override the libraries
```
cp pubspec_overrides.yaml.disabled pubspec_overrides.yaml
```
or save some time with the following script:
```
./scripts/enable_local_dev.sh
```
8. Test them in the [example](./example) and add changes in there if necessary
9. Mention the new changes in the [CHANGELOG.md](../CHANGELOG.md) in the next block
10. Run the following script if possible
```
./scripts/before-push.sh
```
11. When you are done sending your pull request, run:
```
git add .
git commit -m "Your commit message"
git push origin your-branch-name
```
this will push the new branch to your forked repository
12. Now you can send your pull request either by following the link that you will get in the command line or open your
forked repository. You will find an option to send the pull request, you can also
open the [Pull Requests](https://github.com/singerdmx/flutter-quill) tab and send new pull request
13. Please wait for the review, and we might ask you to make more changes, then run:
```
git add .
git commit -m "Your new commit message"
git push origin your-branch-name
```
Thank you for your time and efforts in this open-source community project!!
## Development Notes
Please read the [Development Notes](./doc/development_notes.md) as they are important while development

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Xin Yao
Copyright (c) 2023 Xin Yao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -12,7 +12,7 @@
[![Watch on GitHub][github-forks-badge]][github-forks-link]
[license-badge]: https://img.shields.io/github/license/singerdmx/flutter-quill.svg?style=for-the-badge
[license-link]: https://github.com/singerdmx/flutter-quill/blob/master/LICENSE
[license-link]: ./LICENSE
[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
[prs-link]: https://github.com/singerdmx/flutter-quill/issues
[github-watch-badge]: https://img.shields.io/github/watchers/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff
@ -26,51 +26,51 @@
FlutterQuill is a rich text editor and a [Quill] component for [Flutter].
This library is a WYSIWYG editor built for the modern Android, iOS, web and desktop platforms. Check out our [Youtube Playlist] or [Code Introduction] to take a detailed walkthrough of the code base. You can join our [Slack Group] for discussion.
This library is a WYSIWYG (What You See Is What You Get) editor built
for the modern Android, iOS,
web and desktop platforms.
Check out our [Youtube Playlist] or [Code Introduction](./doc/code_introduction.md)
to take a detailed walkthrough of the code base.
You can join our [Slack Group] for discussion.
Pub: [FlutterQuill]
Pub: [FlutterQuill] <br>
If you are viewing this page from pub.dev page, then you
might have some issues with opening some links, please open
it in GitHub repo instead.
## Table of contents
- [Flutter Quill](#flutter-quill)
- [Table of contents](#table-of-contents)
- [Demo](#demo)
- [Screenshots](#screenshots)
- [Installation](#installation)
- [Usage](#usage)
- [Migration](#migration)
- [Input / Output](#input--output)
- [Links](#links)
- [Configurations](#configurations)
- [Using Custom App Widget](#using-custom-app-widget)
- [Font Size](#font-size)
- [Links](#links-1)
- [Font Family](#font-family)
- [Custom Buttons](#custom-buttons)
- [Embed Blocks](#embed-blocks)
- [Using the embed blocks from `flutter_quill_extensions`](#using-the-embed-blocks-from-flutter_quill_extensions)
- [Custom Size Image for Mobile](#custom-size-image-for-mobile)
- [Custom Size Image for other platforms (excluding web)](#custom-size-image-for-other-platforms-excluding-web)
- [Custom Embed Blocks](#custom-embed-blocks)
- [Custom Toolbar](#custom-toolbar)
- [Translation](#translation)
- [](#)
- [Contributing to translations](#contributing-to-translations)
- [Links](#links-2)
- [Conversion to HTML](#conversion-to-html)
- [Translation](#translation)
- [Testing](#testing)
- [License](#license)
- [Contributors](#contributors)
- [Sponsors](#sponsors)
## Demo
## Screenshots
<p float="left">
<img width="400" alt="1" src="https://user-images.githubusercontent.com/122956/103142422-9bb19c80-46b7-11eb-83e4-dd0538a9236e.png">
<img width="400" alt="1" src="https://user-images.githubusercontent.com/122956/103142455-0531ab00-46b8-11eb-89f8-26a77de9227f.png">
</p>
<details>
<summary>Tap to show/hide screenshots</summary>
<p float="left">
<img width="400" alt="1" src="https://user-images.githubusercontent.com/122956/102963021-f28f5a00-449c-11eb-8f5f-6e9dd60844c4.png">
<img width="400" alt="1" src="https://user-images.githubusercontent.com/122956/102977404-c9c88e00-44b7-11eb-9423-b68f3b30b0e0.png">
</p>
<br>
---
<img src="./example/assets/images/screenshot_1.png" width="250" alt="Screenshot 1">
<img src="./example/assets/images/screenshot_2.png" width="200" alt="Screenshot 2">
<img src="./example/assets/images/screenshot_3.png" width="175" alt="Screenshot 3">
<img src="./example/assets/images/screenshot_4.png" width="135" alt="Screenshot 4">
</details>
## Installation
@ -87,24 +87,37 @@ dependencies:
git: https://github.com/singerdmx/flutter-quill.git
```
> **Important note**
>
> Currently, we're in the process of refactoring the library's configurations. We're actively working on this, and while we don't have a development version available at the moment, your feedback is essential to us.
> Note: At this time, we are making too many changes to the library, and you might see a new version almost every day
>
> Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement of the library. Your input and insights are valuable in shaping a stable and reliable version for all our users. Thank you for being part of the open-source community!
> Using the latest version and reporting any issues you encounter on GitHub will greatly contribute to the improvement of the library.
> Your input and insights are valuable in shaping a stable and reliable version for all our users. Thank you for being part of the open-source community!
>
> also [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) will not work with the latest versions, please use [fresh_quill_extensions](https://pub.dev/packages/fresh_quill_extensions) as temporary alternative
> If the latest version of [FlutterQuill Extensions] is pre-release, then please use it to work with the latest stable version of [FlutterQuill]
>
Compatible versions:
| flutter_quill | flutter_quill_extensions | flutter_quill_test |
|-------------------------|--------------------------|-------------------------|
| 8.5.x | 0.6.x | 0.0.5 |
| 8.5.1 | 0.6.7 | 0.0.5 |
| 8.5.0 | 0.6.7 | 0.0.5 |
These versions are tested and well-supported, you shouldn't get a build failure
## Usage
See the `example` directory for a minimal example of how to use FlutterQuill. You typically just need to instantiate a controller:
First, you need to instantiate a controller
```dart
QuillController _controller = QuillController.basic();
```
and then embed the toolbar and the editor, within your app. For example:
And then use the `QuillEditor`, `QuillToolbar` widgets,
connect the `QuillController` to them
using `QuillProvider` inherited widget
```dart
QuillProvider(
@ -129,147 +142,75 @@ QuillProvider(
)
```
And depending on your use case, you might want to dispose the `_controller` in dispose mehtod
And depending on your use case, you might want to dispose the `_controller` in dispose method
in most cases, it's better to.
Check out [Sample Page] for more advanced usage.
## Migration
We have recently add [migration guide](/doc/migration.md) for migration from different versions
Starting from version `8.0.0`
We have added [Migration Guide](/doc/migration.md) for migration from different versions
## Input / Output
This library uses [Quill] as an internal data format.
This library uses [Quill Delta](https://quilljs.com/docs/delta/)
to represent the document content.
The Delta format is a compact and versatile way to describe document changes.
It consists of a series of operations, each representing an insertion, deletion,
or formatting change within the document.
Don’t be confused by its name Delta—Deltas represents both documents and changes to documents.
If you think of Deltas as the instructions from going from one document to another,
the way Deltas represent a document is by expressing the instructions starting from an empty document.
* Use `_controller.document.toDelta()` to extract the deltas.
* Use `_controller.document.toPlainText()` to extract plain text.
FlutterQuill provides some JSON serialization support, so that you can save and open documents. To save a document as JSON, do something like the following:
FlutterQuill provides some JSON serialization support, so that you can save and open documents.
To save a document as JSON, do something like the following:
```dart
var json = jsonEncode(_controller.document.toDelta().toJson());
final json = jsonEncode(_controller.document.toDelta().toJson());
```
You can then write this to storage.
To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this:
To open a FlutterQuill editor with an existing JSON representation that you've previously stored,
you can do something like this:
```dart
var myJSON = jsonDecode(r'{"insert":"hello\n"}');
_controller = QuillController(
document: Document.fromJson(myJSON),
selection: TextSelection.collapsed(offset: 0),
);
```
final json = jsonDecode(r'{"insert":"hello\n"}');
## Configurations
The `QuillToolbar` class lets you customize which formatting options are available.
[Sample Page] provides sample code for advanced usage and configuration.
For **web development**, use `flutter config --enable-web` for flutter or use [ReactQuill] for React.
It is required to provide `EmbedBuilder`, e.g. [defaultEmbedBuildersWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L99).
Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L317).
For **desktop platforms** It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L297).
### Using Custom App Widget
This project use some adaptive widgets like `AdaptiveTextSelectionToolbar` which require the following delegates:
1. Default Material Localizations delegate
2. Default Cupertino Localizations delegate
3. Defualt Widgets Localizations delegate
You don't need to include those since there are defined by default
but if you are using Custom app or you are overriding the `localizationsDelegates` in the App widget
then please make sure it's including those:
```dart
localizationsDelegates: const [
DefaultCupertinoLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
_controller.document = Document.fromJson(json);
```
And you might need more depending on your use case, for example if you are using custom localizations for your app, using custom app widget like [FluentApp](https://pub.dev/packages/fluent_ui)
which will also need
```dart
localizationsDelegates: const [
// Required localizations delegates ...
FluentLocalizations.delegate,
AppLocalizations.delegate,
],
```
in addition to the required delegates by this library
### Font Size
### Links
Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`.
- [Quill Delta](https://quilljs.com/docs/delta/)
- [Quill Delta Formats](https://quilljs.com/docs/formats)
- [Why Quill](https://quilljs.com/guides/why-quill/)
- [Quill JS Configurations](https://quilljs.com/docs/configuration/)
- [Quill JS Interactive Playground](https://quilljs.com/playground/)
- [Quill JS GitHub repo](https://github.com/quilljs/quill)
When enabled, the default font-size values can be modified via _optional_ `fontSizeValues`. `fontSizeValues` accepts a `Map<String, String>` consisting of a `String` title for the font size and a `String` value for the font size. Example:
```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'}
```
## Configurations
Font size can be cleared with a value of `0`, for example:
The `QuillToolbar` and `QuillEditor` widgets lets you customize a lot of things
[Sample Page] provides sample code for advanced usage and configuration.
```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'}
```
### Links
- [Using Custom App Widget](./doc/configurations/using_custom_app_widget.md)
- [Localizations Setup](./doc/configurations/localizations_setup.md)
- [Font Size](./doc/configurations/font_size.md)
- [Font Family](#font-family)
- [Custom Toolbar buttons](./doc/configurations/custom_buttons.md)
### Font Family
To use your own fonts, update your [assets folder](https://github.com/singerdmx/flutter-quill/tree/master/example/assets/fonts) and pass in `fontFamilyValues`. More details at [this change](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa), [this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) and [this](https://www.flutterbeads.com/change-font-family-flutter/).
### Custom Buttons
You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` of `QuillCustomButton`.
To add an Icon, we should use a new QuillCustomButton class
```dart
QuillCustomButton(
iconData: Icons.ac_unit,
onTap: () {
debugPrint('snowflake');
}
),
```
Each `QuillCustomButton` is used as part of the `customButtons` option as follows:
```dart
QuillToolbar(
configurations: QuillToolbarConfigurations(
customButtons: [
QuillCustomButton(
iconData: Icons.ac_unit,
onTap: () {
debugPrint('snowflake1');
},
),
QuillCustomButton(
iconData: Icons.ac_unit,
onTap: () {
debugPrint('snowflake2');
},
),
QuillCustomButton(
iconData: Icons.ac_unit,
onTap: () {
debugPrint('snowflake3');
},
),
],
),
),
```
To use your own fonts, update your [assets folder](./example/assets/fonts) and pass in `fontFamilyValues`.
More details on [this commit](https://github.com/singerdmx/flutter-quill/commit/71d06f6b7be1b7b6dba2ea48e09fed0d7ff8bbaa),
[this article](https://stackoverflow.com/questions/55075834/fontfamily-property-not-working-properly-in-flutter) and [this](https://www.flutterbeads.com/change-font-family-flutter/).
## Embed Blocks
@ -279,454 +220,45 @@ Provide a list of embed
### Using the embed blocks from `flutter_quill_extensions`
```dart
QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.toolbarButtons(
imageButtonOptions: QuillToolbarImageButtonOptions(
onImagePickCallback: (file) async {
return file.path;
},
),
),
),
),
```
```dart
Expanded(
child: QuillEditor.basic(
configurations: QuillEditorConfigurations(
readOnly: true,
embedBuilders: FlutterQuillEmbeds.editorBuilders(
imageEmbedConfigurations:
const QuillEditorImageEmbedConfigurations(
forceUseMobileOptionMenuForImageClick: true,
),
),
),
),
)
```
<!-- This should be added in the extensions package for better organization -->
<!-- > [!WARNING]
>
> If you are using [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) package to add support for images, videos and more
> The extensions package require additional configurations:
>
> 1. We are using [`gal`](https://github.com/natsuk4ze/) plugin to save images.
> For this to work, you need to add the appropriate permissions
> to your `Info.plist` and `AndroidManifest.xml` files.
> See <https://github.com/natsuk4ze/gal#-get-started> to add the needed lines.
>
> 2. We also use [`image_picker`](https://pub.dev/packages/image_picker) plugin for picking images so please make sure follow the instructions
>
> 3. For loading the image from the internet we need internet permission
> 1. For Android, you need to add some permissions in `AndroidManifest.xml`, Please follow this [link](https://developer.android.com/training/basics/network-ops/connecting) for more info, the internet permission included by default only for debugging so you need to follow this link to add it in the release version too. you should allow loading images and videos only for the `https` protocol but if you want http too then you need to configure your android application to accept `http` in the release mode, follow this [link](https://stackoverflow.com/questions/45940861/android-8-cleartext-http-traffic-not-permitted) for more info.
> 2. for macOS you also need to include a key in your `Info.plist`, please follow this [link](https://stackoverflow.com/a/61201081/18519412) to add the required configurations
>
> The extensions package also use [image_picker](https://pub.dev/packages/image_picker) which also require some configurations, follow this [link](https://pub.dev/packages/image_picker#installation). It's needed for Android, iOS, macOS, we must inform you that you can't pick photo using camera in desktop so make sure to handle that if you plan on add support for desktop, this might changed in the future and for more info follow this [link](https://pub.dev/packages/image_picker#windows-macos-and-linux) -->
### Custom Size Image for Mobile
Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follows:
```dart
{
"insert": {
"image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
},
"attributes":{
"style":"mobileWidth: 50; mobileHeight: 50; mobileMargin: 10; mobileAlignment: topLeft"
}
}
```
### Custom Size Image for other platforms (excluding web)
Define `width`, `height`, `margin`, `alignment` as follows:
```dart
{
"insert": {
"image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
},
"attributes":{
"style":"width: 50; height: 50; margin: 10; alignment: topLeft"
}
}
```
### Custom Embed Blocks
To see how to use the extension package, please take a look at the [README](./flutter_quill_extensions/README.md) of [FlutterQuill Extensions]
Sometimes you want to add some custom content inside your text, custom widgets inside of them. An example is adding notes to the text, or anything custom that you want to add in your text editor.
### Links
The only thing that you need is to add a `CustomBlockEmbed` and provider a builder for it to the `embedBuilders` parameter, to transform the data inside of the Custom Block into a widget!
Here is an example:
Starting with the `CustomBlockEmbed`, here we extend it and add the methods that are useful for the 'Note' widget, that will be the `Document`, used by the `flutter_quill` to render the rich text.
```dart
class NotesBlockEmbed extends CustomBlockEmbed {
const NotesBlockEmbed(String value) : super(noteType, value);
static const String noteType = 'notes';
static NotesBlockEmbed fromDocument(Document document) =>
NotesBlockEmbed(jsonEncode(document.toDelta().toJson()));
Document get document => Document.fromJson(jsonDecode(data));
}
```
After that, we need to map this "notes" type into a widget. In that case, I used a `ListTile` with a text to show the plain text resume of the note, and the `onTap` function to edit the note.
Don't forget to add this method to the `QuillEditor` after that!
```dart
class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
@override
String get key => 'notes';
@override
Widget build(
BuildContext context,
QuillController controller,
Embed node,
bool readOnly,
bool inline,
TextStyle textStyle,
) {
final notes = NotesBlockEmbed(node.value.data).document;
return Material(
color: Colors.transparent,
child: ListTile(
title: Text(
notes.toPlainText().replaceAll('\n', ' '),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
leading: const Icon(Icons.notes),
onTap: () => addEditNote(context, document: notes),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.grey),
),
),
);
}
}
```
And finally, we write the function to add/edit this note. The `showDialog` function shows the QuillEditor to edit the note, after the user ends the edition, we check if the document has something, and if it has, we add or edit the `NotesBlockEmbed` inside of a `BlockEmbed.custom` (this is a little detail that will not work if you don't pass the `CustomBlockEmbed` inside of a `BlockEmbed.custom`).
```dart
Future<void> _addEditNote(BuildContext context, {Document? document}) async {
final isEditing = document != null;
final quillEditorController = QuillController(
document: document ?? Document(),
selection: const TextSelection.collapsed(offset: 0),
);
await showDialog(
context: context,
builder: (context) => AlertDialog(
titlePadding: const EdgeInsets.only(left: 16, top: 8),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${isEditing ? 'Edit' : 'Add'} note'),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
)
],
),
content: QuillEditor.basic(
controller: quillEditorController,
readOnly: false,
),
),
);
if (quillEditorController.document.isEmpty()) return;
final block = BlockEmbed.custom(
NotesBlockEmbed.fromDocument(quillEditorController.document),
);
final controller = _controller!;
final index = controller.selection.baseOffset;
final length = controller.selection.extentOffset - index;
if (isEditing) {
final offset = getEmbedNode(controller, controller.selection.start).offset;
controller.replaceText(
offset, 1, block, TextSelection.collapsed(offset: offset));
} else {
controller.replaceText(index, length, block, null);
}
}
```
And voila, we have a custom widget inside of the rich text editor!
<p float="left">
<img width="400" alt="1" src="https://i.imgur.com/yBTPYeS.png">
</p>
> 1. For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877)
> 2. For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc)
### Custom Toolbar
If you want to use custom toolbar but still want the support of this libray
You can use the `QuillBaseToolbar` which is the base for the `QuillToolbar`
> If you are using the toolbar buttons like `QuillToolbarHistoryButton`, `QuillToolbarToggleStyleButton` in the somewhere like the the custom toolbar then you must provide them with `QuillToolbarProvider` inherited widget, you don't have to do this if you are using the `QuillToolbar` since it will be done for you
Example:
```dart
QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(),
),
child: Column(
children: [
QuillToolbarProvider(
toolbarConfigurations: const QuillToolbarConfigurations(),
child: QuillBaseToolbar(
configurations: QuillBaseToolbarConfigurations(
toolbarSize: 15 * 2,
multiRowsDisplay: false,
childrenBuilder: (context) {
final controller = context.requireQuillController;
return [
QuillToolbarHistoryButton(
controller: controller,
options: const QuillToolbarHistoryButtonOptions(
isUndo: true),
),
QuillToolbarHistoryButton(
controller: controller,
options: const QuillToolbarHistoryButtonOptions(
isUndo: false),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_bold,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_italic,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_underline,
iconSize: 20,
),
),
QuillToolbarClearFormatButton(
controller: controller,
options: const QuillToolbarClearFormatButtonOptions(
iconData: Icons.format_clear,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options:
const QuillToolbarSelectHeaderStyleButtonsOptions(
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_numbered,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_bulleted,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.blockQuote,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_quote,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarIndentButton(
controller: controller,
isIncrease: true,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_increase,
iconSize: 20,
)),
QuillToolbarIndentButton(
controller: controller,
isIncrease: false,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_decrease,
iconSize: 20,
),
),
];
},
),
),
),
Expanded(
child: QuillEditor.basic(
configurations: const QuillEditorConfigurations(
readOnly: false,
placeholder: 'Write your notes',
padding: EdgeInsets.all(16),
),
),
)
],
),
)
```
if you want more customized toolbar feel free to create your own and use the `controller` to interact with the editor. checkout the `QuillToolbar` and the buttons inside it to see an example of how that will works
### Translation
The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your own locale with:
```dart
QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('fr'),
),
),
child: Column(
children: [
const QuillToolbar(
configurations: QuillToolbarConfigurations(),
),
Expanded(
child: QuillEditor.basic(
configurations: const QuillEditorConfigurations(),
),
)
],
),
)
```
###
Currently, translations are available for these 31 locales:
* `Locale('en')`
* `Locale('ar')`
* `Locale('bn')`
* `Locale('bs')`
* `Locale('cs')`
* `Locale('de')`
* `Locale('da')`
* `Locale('fr')`
* `Locale('he')`
* `Locale('zh', 'cn')`
* `Locale('zh', 'hk')`
* `Locale('ko')`
* `Locale('ru')`
* `Locale('es')`
* `Locale('tk')`
* `Locale('tr')`
* `Locale('uk')`
* `Locale('ur')`
* `Locale('pt')`
* `Locale('pl')`
* `Locale('vi')`
* `Locale('id')`
* `Locale('it')`
* `Locale('ms')`
* `Locale('nl')`
* `Locale('no')`
* `Locale('fa')`
* `Locale('hi')`
* `Locale('sr')`
* `Locale('sw')`
* `Locale('ja')`
#### Contributing to translations
The translation file is located at [toolbar.i18n.dart](lib/src/translations/toolbar.i18n.dart). Feel free to contribute your own translations, just copy the English translations map and replace the values with your translations. Then open a pull request so everyone can benefit from your translations!
- [Custom Embed Blocks](./doc/custom_embed_blocks.md)
- [Custom Toolbar](./doc/custom_toolbar.md)
## Conversion to HTML
Having your document stored in Quill Delta format is sometimes not enough. Often you'll need to convert
it to other formats such as HTML in order to publish it, or send an email. One option is to use
it to other formats such as HTML to publish it, or send an email.
You have two options:
1. Using [quill_html_converter](./packages/quill_html_converter/) to convert to/from HTML, the package can convert the Quill delta to HTML well
(it uses [vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html)) but the converting from HTML back to Quill delta is experimental
2. Another option is to use
[vsc_quill_delta_to_html](https://pub.dev/packages/vsc_quill_delta_to_html) to convert your document
to HTML. This package has full support for all Quill operations - including images, videos, formulas,
tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter.
to HTML.
This package has full support for all Quill operations—including images, videos, formulas,
tables, and mentions.
Conversion can be performed in vanilla Dart (i.e., server-side or CLI) or in Flutter.
It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html)
Typescript/Javascript package.
this package doesn't convert the HTML back to Quill Delta as far as we know
## Testing
To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases.
Import the test utilities in your test file:
## Translation
```dart
import 'package:flutter_quill/flutter_quill_test.dart';
```
The package offers translations for the quill toolbar and editor, it will follow the system locale unless you set your own locale.
and then enter text using `quillEnterText`:
```dart
await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
```
Open this [page](./doc/translation.md) for more info
## License
## Testing
[MIT](LICENSE)
Please use [flutter_quill_test](https://pub.dev/packages/flutter_quill_test) for testing
## Contributors
Special thanks for everyone that have contributed to this project...
Special thanks for everyone that has contributed to this project...
<a href="https://github.com/singerdmx/flutter-quill/graphs/contributors">
<img src="https://contrib.rocks/image?repo=singerdmx/flutter-quill" />
@ -736,15 +268,22 @@ Special thanks for everyone that have contributed to this project...
Made with [contrib.rocks](https://contrib.rocks).
We welcome contributions!
Please follow these guidelines when contributing to the project. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details. <br>
We must mention that the [CONTRIBUTING.md](./CONTRIBUTING.md) have a development notes, so if you're planning on contributing to the repo,
please consider reading it.
You can check the [Todo](./doc/todo.md) list if you want to
[Quill]: https://quilljs.com/docs/formats
[Flutter]: https://github.com/flutter/flutter
[FlutterQuill]: https://pub.dev/packages/flutter_quill
[FlutterQuill Extensions]: https://pub.dev/packages/flutter_quill_extensions
[ReactQuill]: https://github.com/zenoamaro/react-quill
[Youtube Playlist]: https://youtube.com/playlist?list=PLbhaS_83B97vONkOAWGJrSXWX58et9zZ2
[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g
[Sample Page]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart
[Code Introduction]: https://github.com/singerdmx/flutter-quill/blob/master/CodeIntroduction.md
<hr/>
[Sample Page]: ./example/lib/pages/home_page.dart
[FluentUI]: https://pub.dev/packages/fluent_ui
[中文文档](./doc_cn.md)

@ -1,4 +1,4 @@
include: package:pedantic/analysis_options.yaml
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
@ -6,32 +6,31 @@ analyzer:
unsafe_html: ignore
linter:
rules:
- always_declare_return_types
- always_put_required_named_parameters_first
- annotate_overrides
- avoid_empty_else
- avoid_escaping_inner_quotes
- avoid_print
- avoid_redundant_argument_values
- avoid_types_on_closure_parameters
- avoid_void_async
- cascade_invocations
- directives_ordering
- lines_longer_than_80_chars
- omit_local_variable_types
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_initializing_formals
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_relative_imports
- prefer_single_quotes
- sort_constructors_first
- sort_unnamed_constructors_first
- unnecessary_lambdas
- unnecessary_parenthesis
- unnecessary_string_interpolations
always_declare_return_types: true
always_put_required_named_parameters_first: true
annotate_overrides: true
avoid_empty_else: true
avoid_escaping_inner_quotes: true
avoid_print: true
avoid_redundant_argument_values: true
avoid_types_on_closure_parameters: true
avoid_void_async: true
cascade_invocations: true
directives_ordering: true
omit_local_variable_types: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: true
prefer_final_fields: true
prefer_final_in_for_each: true
prefer_final_locals: true
prefer_initializing_formals: true
prefer_int_literals: true
prefer_interpolation_to_compose_strings: true
prefer_relative_imports: true
prefer_single_quotes: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
unnecessary_lambdas: true
unnecessary_parenthesis: true
unnecessary_string_interpolations: true

@ -0,0 +1,43 @@
# Custom `QuillToolbar` Buttons
You may add custom buttons to the _end_ of the toolbar, via the `customButtons` option, which is a `List` of `QuillToolbarCustomButtonOptions`.
To add an Icon, we should use a new `QuillToolbarCustomButtonOptions` class
```dart
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit),
tooltip: '',
onPressed: () {},
afterButtonPressed: () {},
),
```
Each `QuillCustomButton` is used as part of the `customButtons` option as follows:
```dart
QuillToolbar(
configurations: QuillToolbarConfigurations(
customButtons: [
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit),
onPressed: () {
debugPrint('snowflake1');
},
),
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit),
onPressed: () {
debugPrint('snowflake2');
},
),
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit),
onPressed: () {
debugPrint('snowflake3');
},
),
],
),
),
```

@ -0,0 +1,15 @@
# Font Size
Within the editor toolbar, a drop-down with font-sizing capabilities is available. This can be enabled or disabled with `showFontSize`.
When enabled, the default font-size values can be modified via _optional_ `fontSizeValues`. `fontSizeValues` accepts a `Map<String, String>` consisting of a `String` title for the font size and a `String` value for the font size. Example:
```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46'}
```
Font size can be cleared with a value of `0`, for example:
```dart
fontSizeValues: const {'Small': '8', 'Medium': '24.5', 'Large': '46', 'Clear': '0'}
```

@ -0,0 +1,25 @@
# Localizations Setup
in addition to the required delegatess which mentioned above in [Using custom app widget](./using_custom_app_widget.md)
which are:
```dart
localizationsDelegates: const [
DefaultCupertinoLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
```
which are used by offical flutter widgets
The library also needs the
```dart
// Required localizations delegates ...
FlutterQuillLocalizations.delegate
```
To offer the default localizations.
But **you don't have to** since we have wraped the `QuillEditor` and `QuillToolbar` with `FlutterQuillLocalizationsWidget` which will check if it sets then it will go, if not, then it will be provided only for them, so it's not really required, but if you are overriding the `localizationsDelegates` you could also add the `FlutterQuillLocalizations.delegate`
which won't change anything
There are additional notes in the [Translation](../translation.md) section

@ -0,0 +1,34 @@
# Using Custom App Widget
This project use some adaptive widgets like `AdaptiveTextSelectionToolbar` which require the following delegates:
1. Default Material Localizations delegate
2. Default Cupertino Localizations delegate
3. Defualt Widgets Localizations delegate
You don't need to include those since there are defined by default
but if you are using Custom app or you are overriding the `localizationsDelegates` in the App widget
then please make sure it's including those:
```dart
localizationsDelegates: const [
DefaultCupertinoLocalizations.delegate,
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
],
```
And you might need more depending on your use case, for example if you are using custom localizations for your app, using custom app widget like `FluentApp` from [FluentUI]
which will also need
```dart
localizationsDelegates: const [
// Required localizations delegates ...
FluentLocalizations.delegate,
AppLocalizations.delegate,
],
```
Note: In the latest versions of `FluentApp` you no longer need to add the `localizationsDelegates` but this is just an example, for more [info](https://github.com/bdlukaa/fluent_ui/pull/946)
There are additonal notes in [Localizations](./localizations_setup.md) page

@ -0,0 +1,124 @@
# Custom Embed Blocks
Sometimes you want to add some custom content inside your text, custom widgets inside of them. An example is adding notes to the text, or anything custom that you want to add in your text editor.
The only thing that you need is to add a `CustomBlockEmbed` and provider a builder for it to the `embedBuilders` parameter, to transform the data inside of the Custom Block into a widget!
Here is an example:
Starting with the `CustomBlockEmbed`, here we extend it and add the methods that are useful for the 'Note' widget, that will be the `Document`, used by the `flutter_quill` to render the rich text.
```dart
class NotesBlockEmbed extends CustomBlockEmbed {
const NotesBlockEmbed(String value) : super(noteType, value);
static const String noteType = 'notes';
static NotesBlockEmbed fromDocument(Document document) =>
NotesBlockEmbed(jsonEncode(document.toDelta().toJson()));
Document get document => Document.fromJson(jsonDecode(data));
}
```
After that, we need to map this "notes" type into a widget. In that case, I used a `ListTile` with a text to show the plain text resume of the note, and the `onTap` function to edit the note.
Don't forget to add this method to the `QuillEditor` after that!
```dart
class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
@override
String get key => 'notes';
@override
Widget build(
BuildContext context,
QuillController controller,
Embed node,
bool readOnly,
bool inline,
TextStyle textStyle,
) {
final notes = NotesBlockEmbed(node.value.data).document;
return Material(
color: Colors.transparent,
child: ListTile(
title: Text(
notes.toPlainText().replaceAll('\n', ' '),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
leading: const Icon(Icons.notes),
onTap: () => addEditNote(context, document: notes),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.grey),
),
),
);
}
}
```
And finally, we write the function to add/edit this note. The `showDialog` function shows the QuillEditor to edit the note, after the user ends the edition, we check if the document has something, and if it has, we add or edit the `NotesBlockEmbed` inside of a `BlockEmbed.custom` (this is a little detail that will not work if you don't pass the `CustomBlockEmbed` inside of a `BlockEmbed.custom`).
```dart
Future<void> _addEditNote(BuildContext context, {Document? document}) async {
final isEditing = document != null;
final quillEditorController = QuillController(
document: document ?? Document(),
selection: const TextSelection.collapsed(offset: 0),
);
await showDialog(
context: context,
builder: (context) => AlertDialog(
titlePadding: const EdgeInsets.only(left: 16, top: 8),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('${isEditing ? 'Edit' : 'Add'} note'),
IconButton(
onPressed: () => Navigator.of(context).pop(),
icon: const Icon(Icons.close),
)
],
),
content: QuillEditor.basic(
controller: quillEditorController,
readOnly: false,
),
),
);
if (quillEditorController.document.isEmpty()) return;
final block = BlockEmbed.custom(
NotesBlockEmbed.fromDocument(quillEditorController.document),
);
final controller = _controller!;
final index = controller.selection.baseOffset;
final length = controller.selection.extentOffset - index;
if (isEditing) {
final offset = getEmbedNode(controller, controller.selection.start).offset;
controller.replaceText(
offset, 1, block, TextSelection.collapsed(offset: offset));
} else {
controller.replaceText(index, length, block, null);
}
}
```
And voila, we have a custom widget inside of the rich text editor!
<p float="left">
<img width="400" alt="1" src="https://i.imgur.com/yBTPYeS.png">
</p>
> 1. For more info and a video example, see the [PR of this feature](https://github.com/singerdmx/flutter-quill/pull/877)
> 2. For more details, check out [this YouTube video](https://youtu.be/pI5p5j7cfHc)

@ -0,0 +1,143 @@
# Custom Toolbar
If you want to use custom toolbar but still want the support of this libray
You can use the `QuillBaseToolbar` which is the base for the `QuillToolbar`
> If you are using the toolbar buttons like `QuillToolbarHistoryButton`, `QuillToolbarToggleStyleButton` in the somewhere like the the custom toolbar then you must provide them with `QuillToolbarProvider` inherited widget, you don't have to do this if you are using the `QuillToolbar` since it will be done for you
Example:
```dart
QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(),
),
child: Column(
children: [
QuillToolbarProvider(
toolbarConfigurations: const QuillToolbarConfigurations(),
child: QuillBaseToolbar(
configurations: QuillBaseToolbarConfigurations(
toolbarSize: 15 * 2,
multiRowsDisplay: false,
childrenBuilder: (context) {
final controller = context.requireQuillController;
return [
QuillToolbarHistoryButton(
controller: controller,
options: const QuillToolbarHistoryButtonOptions(
isUndo: true),
),
QuillToolbarHistoryButton(
controller: controller,
options: const QuillToolbarHistoryButtonOptions(
isUndo: false),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_bold,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_italic,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_underline,
iconSize: 20,
),
),
QuillToolbarClearFormatButton(
controller: controller,
options: const QuillToolbarClearFormatButtonOptions(
iconData: Icons.format_clear,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options:
const QuillToolbarSelectHeaderStyleButtonsOptions(
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_numbered,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_bulleted,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.blockQuote,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_quote,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarIndentButton(
controller: controller,
isIncrease: true,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_increase,
iconSize: 20,
)),
QuillToolbarIndentButton(
controller: controller,
isIncrease: false,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_decrease,
iconSize: 20,
),
),
];
},
),
),
),
Expanded(
child: QuillEditor.basic(
configurations: const QuillEditorConfigurations(
readOnly: false,
placeholder: 'Write your notes',
padding: EdgeInsets.all(16),
),
),
)
],
),
)
```
if you want more customized toolbar feel free to create your own and use the `controller` to interact with the editor. checkout the `QuillToolbar` and the buttons inside it to see an example of how that will works

@ -0,0 +1,3 @@
# Development notes
- When update the translations or localizations in the app, please take a look at the [Translation](./translation.md) page as it have important notes in order to work, if you also adding a feature that add new localizations then you need to the instructions of it in order for the translations to take affect

@ -79,10 +79,14 @@ All the options have parent `QuillToolbarBaseButtonOptions` which have common th
final IconData? iconData;
/// To change the icon size pass a different value, by default will be
/// [kDefaultIconSize]
/// [kDefaultIconSize].
/// This will be used for all the buttons but you can override this
final double globalIconSize;
/// The factor of how much larger the button is in relation to the icon,
/// by default it will be [kIconButtonFactor].
final double globalIconButtonFactor;
/// To do extra logic after pressing the button
final VoidCallback? afterButtonPressed;

@ -24,7 +24,7 @@
---
> This documentation is outdated. Please check the English version.
> This documentation is outdated. Please check the [English version](../../README.md).
`FlutterQuill` 是一个富文本编辑器,也是 [Quill](https://quilljs.com/docs/formats) 在 [Flutter](https://github.com/flutter/flutter) 的版本

@ -0,0 +1,48 @@
# Todo
This is a todo list page that added recently and will be updated soon.
## Table of contents
- [Todo](#todo)
- [Table of contents](#table-of-contents)
- [Flutter Quill](#flutter-quill)
- [Features](#features)
- [Improvemenets](#improvemenets)
- [Bugs](#bugs)
- [Flutter Quill Extensions](#flutter-quill-extensions)
- [Features](#features-1)
- [Improvemenets](#improvemenets-1)
- [Bugs](#bugs-1)
## Flutter Quill
### Features
- Add support for Text magnification feature, for more [info](https://github.com/singerdmx/flutter-quill/issues/1504)
- Provide a way to expose quills undo redo stacks, for more [info](https://github.com/singerdmx/flutter-quill/issues/1381)
- Add callback to the `QuillToolbarColorButton` for custom color picking logic
### Improvemenets
- Improve the Raw Quill Editor, for more [info](https://github.com/singerdmx/flutter-quill/issues/1509)
- Provide more support to all the platforms
- Extract the shared properties between `QuillRawEditorConfigurations` and `QuillEditorConfigurations`
### Bugs
Empty for now.
Please go to the [issues](https://github.com/singerdmx/flutter-quill/issues)
## Flutter Quill Extensions
### Features
- Add support for copying images to the Clipboard
### Improvemenets
Please check the todos, this list will be updated soon.
### Bugs
Please check the todos, this list will be updated soon.

@ -0,0 +1,65 @@
# Translation
The package offers translations for the quill toolbar and editor, it will follow the locale that is defined in your `WidgetsApp` for example `MaterialApp` which usually follow the system local and it unless you set your own locale with:
```dart
QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
sharedConfigurations: const QuillSharedConfigurations(
locale: Locale('fr'), // will take affect only if FlutterQuillLocalizations.delegate is not defined in the Widget app
),
),
child: Column(
children: [
const QuillToolbar(
configurations: QuillToolbarConfigurations(),
),
Expanded(
child: QuillEditor.basic(
configurations: const QuillEditorConfigurations(),
),
)
],
),
)
```
Currently, translations are available for these 31 locales:
* `Locale('en')`, `Locale('ar')`, `Locale('bn')`, `Locale('bs')`
* `Locale('cs')`, `Locale('de')`, `Locale('da')`, `Locale('fr')`
* `Locale('he')`, `Locale('zh', 'CN')`, `Locale('zh', 'HK')`, `Locale('ko')`
* `Locale('ru')`, `Locale('es')`, `Locale('tk')`, `Locale('tr')`
* `Locale('uk')`, `Locale('ur')`, `Locale('pt')`, `Locale('pl')`
* `Locale('vi')`, `Locale('id')`, `Locale('it')`, `Locale('ms')`
* `Locale('nl')`, `Locale('no')`, `Locale('fa')`, `Locale('hi')`
* `Locale('sr')`, `Locale('sw')`, `Locale('ja')`
#### Contributing to translations
The translation files is located at [l10n folder](../lib/src/l10n/). Feel free to contribute your own translations, just copy the [English translations](../lib/src/l10n/quill_en.arb) map and replace the values with your translations.
Add new file in the l10n folder with the following name
`quill_${localName}.arb` for example `quill_de.arb`
paste the English version and replace the values
Also you can take a look at the [untranslated.json](../lib/src/l10n/untranslated.json) json file, which is a generated file that tell you which keys with which locales hasn't translated so you can find the missings easily
After you are done and want to test the changes, run the following in the root folder (preferred):
```
flutter gen-l10n
```
or:
```
./scripts/regenerate-translations.sh
```
This will generate the new dart files from the arb files in order to take affect, otherwise you won't notice a difference
Then open a pull request so everyone can benefit from your translations!

10
example/.gitignore vendored

@ -8,6 +8,7 @@
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
@ -31,12 +32,13 @@
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
pubspec.lock
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

@ -4,7 +4,42 @@
# This file should be version controlled and should not be manually edited.
version:
revision: 84f3d28555368a70270e9ac8390a9441df95e752
channel: stable
revision: "d211f42860350d914a5ad8102f9ec32764dc6d06"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: android
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: ios
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: linux
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: macos
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: web
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
- platform: windows
create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

@ -1,16 +1,18 @@
# app
# Demo
demo app
This is just a demo of Flutter Quill
## Getting Started
This project is a starting point for a Flutter application.
## Screenshots
A few resources to get you started if this is your first Flutter project:
<img src="./assets/images/screenshot_1.png" width="150" alt="Screenshot 1">
<img src="./assets/images/screenshot_2.png" width="150" alt="Screenshot 2">
<img src="./assets/images/screenshot_3.png" width="150" alt="Screenshot 3">
<img src="./assets/images/screenshot_4.png" width="150" alt="Screenshot 4">
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
## Development notes
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
- When changing the `assets` please run:
```
dart run build_runner build --delete-conflicting-outputs
```

@ -0,0 +1,37 @@
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
invalid_annotation_target: ignore
linter:
rules:
always_declare_return_types: true
always_put_required_named_parameters_first: true
annotate_overrides: true
avoid_empty_else: true
avoid_escaping_inner_quotes: true
avoid_print: false
avoid_redundant_argument_values: false
avoid_types_on_closure_parameters: true
avoid_void_async: true
cascade_invocations: true
directives_ordering: true
omit_local_variable_types: true
prefer_const_constructors: true
prefer_const_constructors_in_immutables: true
prefer_const_declarations: true
prefer_final_fields: true
prefer_final_in_for_each: true
prefer_final_locals: true
prefer_initializing_formals: true
prefer_int_literals: true
prefer_interpolation_to_compose_strings: true
prefer_relative_imports: true
prefer_single_quotes: true
sort_constructors_first: true
sort_unnamed_constructors_first: true
unnecessary_lambdas: true
unnecessary_parenthesis: true
unnecessary_string_interpolations: true
library_private_types_in_public_api: false

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
@ -21,16 +22,14 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
namespace "com.example.example"
compileSdk flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
@ -42,17 +41,15 @@ android {
}
defaultConfig {
applicationId "com.example.app"
minSdkVersion 21
applicationId "com.example.example"
minSdkVersion 23
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
// Multidex is not required for api level 21
}
buildTypes {
release {
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
@ -62,6 +59,4 @@ flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
dependencies {}

@ -1,8 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Flutter needs it to communicate with the running application
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

@ -1,24 +1,35 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- The camera and gps features will be used -->
<!-- But it's not required to install the app -->
<uses-feature
android:name="android.hardware.location.gps"
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<application
android:name="${applicationName}"
android:label="app"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@ -26,13 +37,18 @@
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- For `image_cropper` plugin -->
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
tools:ignore="LockedOrientationActivity" />
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data

@ -1,6 +1,5 @@
package com.example.app
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
class MainActivity: FlutterActivity()

@ -3,7 +3,7 @@
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Flutter Quill Demo</string>
</resources>

@ -3,7 +3,7 @@
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.

@ -1,8 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app">
<!-- Flutter needs it to communicate with the running application
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

@ -1,12 +1,14 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
ext.kotlin_version = '1.9.0'
ext.kotlin_version = '1.9.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'com.android.tools.build:gradle:8.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -21,6 +23,47 @@ allprojects {
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
// For mode details visit https://gist.github.com/freshtechtips/93fefb39e48c40592bda3931e05fd35c
afterEvaluate {
// check if android block is available
if (it.hasProperty('android')) {
if (it.android.namespace == null) {
def manifest = new XmlSlurper().parse(file(it.android.sourceSets.main.manifest.srcFile))
def packageName = manifest.@package.text()
println("Setting ${packageName} as android namespace in build.gradle from the AndroidManifest.xml")
android.namespace = packageName
}
def javaVersion = JavaVersion.VERSION_17
println("Changes will be applied for the following packages:")
android {
def androidApiVersion = 34
// compileSdkVersion androidApiVersion
compileSdk androidApiVersion
defaultConfig {
targetSdkVersion androidApiVersion
}
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
}
tasks.withType(KotlinCompile).configureEach {
buildscript {
ext.kotlin_version = kotlin_version
}
kotlinOptions {
jvmTarget = javaVersion.toString()
}
}
String message = "For package ${android.namespace} by update compileSdkVersion, targetSdkVersion \n to $androidApiVersion and java version to ${javaVersion.toString()}"
println(message)
}
}
}
}
subprojects {
project.evaluationDependsOn(':app')

@ -1,3 +1,6 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false

@ -1,6 +1,5 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip

@ -1,11 +1,20 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
return flutterSdkPath
}
settings.ext.flutterSdkPath = flutterSdkPath()
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
plugins {
id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
}
}
include ":app"
apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

File diff suppressed because one or more lines are too long

@ -1,521 +0,0 @@
[
{
"insert": "Flutter Quill"
},
{
"attributes": {
"header": 1
},
"insert": "\n"
},
{
"insert": "\nRich text editor for Flutter"
},
{
"attributes": {
"header": 2
},
"insert": "\n"
},
{
"insert": "Quill component for Flutter"
},
{
"attributes": {
"header": 3
},
"insert": "\n"
},
{
"insert": "This "
},
{
"attributes": {
"italic": true,
"background": "transparent"
},
"insert": "library"
},
{
"insert": " supports "
},
{
"attributes": {
"bold": true,
"background": "#ebd6ff"
},
"insert": "mobile"
},
{
"insert": " platform "
},
{
"attributes": {
"underline": true,
"bold": true,
"color": "#e60000"
},
"insert": "only"
},
{
"attributes": {
"color": "rgba(0, 0, 0, 0.847)"
},
"insert": " and "
},
{
"attributes": {
"strike": true,
"color": "black"
},
"insert": "web"
},
{
"insert": " is not supported.\nYou are welcome to use "
},
{
"attributes": {
"link": "https://bulletjournal.us/home/index.html"
},
"insert": "Bullet Journal"
},
{
"insert": ":\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders"
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices"
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Check out what you and your teammates are working on each day"
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "\nSplitting bills with friends can never be easier."
},
{
"attributes": {
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "Start creating a group and invite your friends to join."
},
{
"attributes": {
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "Create a BuJo of Ledger type to see expense or balance summary."
},
{
"attributes": {
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s)."
},
{
"attributes": {
"blockquote": true
},
"insert": "\n"
},
{
"insert": "\nvar BuJo = 'Bullet' + 'Journal'"
},
{
"attributes": {
"code-block": true
},
"insert": "\n"
},
{
"insert": "\nStart tracking in your browser"
},
{
"attributes": {
"indent": 1
},
"insert": "\n"
},
{
"insert": "Stop the timer on your phone"
},
{
"attributes": {
"indent": 1
},
"insert": "\n"
},
{
"insert": "All your time entries are synced"
},
{
"attributes": {
"indent": 2
},
"insert": "\n"
},
{
"insert": "between the phone apps"
},
{
"attributes": {
"indent": 2
},
"insert": "\n"
},
{
"insert": "and the website."
},
{
"attributes": {
"indent": 3
},
"insert": "\n"
},
{
"insert": "\n"
},
{
"insert": "\nCenter Align"
},
{
"attributes": {
"align": "center"
},
"insert": "\n"
},
{
"insert": "Right Align"
},
{
"attributes": {
"align": "right"
},
"insert": "\n"
},
{
"insert": "Justify Align"
},
{
"attributes": {
"align": "justify"
},
"insert": "\n"
},
{
"insert": "Have trouble finding things? "
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Just type in the search bar"
},
{
"attributes": {
"indent": 1,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "and easily find contents"
},
{
"attributes": {
"indent": 2,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "across projects or folders."
},
{
"attributes": {
"indent": 2,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "It matches text in your note or task."
},
{
"attributes": {
"indent": 1,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Enable reminders so that you will get notified by"
},
{
"attributes": {
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "email"
},
{
"attributes": {
"indent": 1,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "message on your phone"
},
{
"attributes": {
"indent": 1,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "popup on the web site"
},
{
"attributes": {
"indent": 1,
"list": "ordered"
},
"insert": "\n"
},
{
"insert": "Create a BuJo serving as project or folder"
},
{
"attributes": {
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "Organize your"
},
{
"attributes": {
"indent": 1,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "tasks"
},
{
"attributes": {
"indent": 2,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "notes"
},
{
"attributes": {
"indent": 2,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "transactions"
},
{
"attributes": {
"indent": 2,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "under BuJo "
},
{
"attributes": {
"indent": 3,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "See them in Calendar"
},
{
"attributes": {
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "or hierarchical view"
},
{
"attributes": {
"indent": 1,
"list": "bullet"
},
"insert": "\n"
},
{
"insert": "this is a check list"
},
{
"attributes": {
"list": "checked"
},
"insert": "\n"
},
{
"insert": "this is a uncheck list"
},
{
"attributes": {
"list": "unchecked"
},
"insert": "\n"
},
{
"insert": "Font "
},
{
"attributes": {
"font": "sans-serif"
},
"insert": "Sans Serif"
},
{
"insert": " "
},
{
"attributes": {
"font": "serif"
},
"insert": "Serif"
},
{
"insert": " "
},
{
"attributes": {
"font": "monospace"
},
"insert": "Monospace"
},
{
"insert": " Size "
},
{
"attributes": {
"size": "small"
},
"insert": "Small"
},
{
"insert": " "
},
{
"attributes": {
"size": "large"
},
"insert": "Large"
},
{
"insert": " "
},
{
"attributes": {
"size": "huge"
},
"insert": "Huge"
},
{
"attributes": {
"size": "15.0"
},
"insert": "font size 15"
},
{
"insert": " "
},
{
"attributes": {
"size": "35"
},
"insert": "font size 35"
},
{
"insert": " "
},
{
"attributes": {
"size": "20"
},
"insert": "font size 20"
},
{
"attributes": {
"token": "built_in"
},
"insert": " diff"
},
{
"attributes": {
"token": "operator"
},
"insert": "-match"
},
{
"attributes": {
"token": "literal"
},
"insert": "-patch"
},
{
"insert": {
"image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
},
"attributes": {
"width": "230",
"style": "display: block; margin: auto;"
}
},
{
"insert": "\n"
}
]

@ -1,3 +1,4 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
@ -18,6 +19,7 @@ Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/

@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>

@ -32,6 +32,9 @@ target 'Runner' do
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|

@ -9,14 +9,23 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
D290BBC2BCE42906E260DD85 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
@ -33,22 +42,19 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
2F435AE316A2CEF9DB2FCB44 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
45FB9682812B691627A14497 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
48A1E3B04AC3F3F79EE01940 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -56,24 +62,12 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D290BBC2BCE42906E260DD85 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
54D582D77359D2F1CFEABAD6 /* Pods */ = {
isa = PBXGroup;
children = (
2F435AE316A2CEF9DB2FCB44 /* Pods-Runner.debug.xcconfig */,
48A1E3B04AC3F3F79EE01940 /* Pods-Runner.release.xcconfig */,
45FB9682812B691627A14497 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -85,14 +79,21 @@
name = Flutter;
sourceTree = "<group>";
};
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
54D582D77359D2F1CFEABAD6 /* Pods */,
9B5485B940DE97CC1A7B761C /* Frameworks */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
@ -100,6 +101,7 @@
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -107,50 +109,49 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
9B5485B940DE97CC1A7B761C /* Frameworks */ = {
isa = PBXGroup;
children = (
DC245C6D0FF6BF1D0B733C5B /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807E294A63A400263BE5 /* Frameworks */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C781F89B47F2E7DB7E2B4898 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
869AA06D856BCA582968F111 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -167,11 +168,17 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
@ -189,11 +196,19 @@
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@ -224,23 +239,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
869AA06D856BCA582968F111 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -256,43 +254,36 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C781F89B47F2E7DB7E2B4898 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
@ -368,27 +359,72 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -489,6 +525,8 @@
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@ -499,23 +537,19 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@ -525,23 +559,18 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@ -549,6 +578,16 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (

@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -38,8 +36,19 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@ -61,8 +70,6 @@
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"

@ -4,7 +4,4 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

@ -1,6 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end

@ -1,13 +0,0 @@
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Example</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -11,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>app</string>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
@ -20,8 +22,6 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Need to save image</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
@ -41,11 +41,17 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need permission to the photo library in order for inserting images in the text editor</string>
<key>NSCameraUsageDescription</key>
<string>We need permission to the camera in order for takeing a photos and record videos in the text editor</string>
<key>NSMicrophoneUsageDescription<key>
<string>We don't really need that permission</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need this permission for saving the images in the editor</string>
</dict>
</plist>

@ -1,9 +0,0 @@
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

@ -0,0 +1,114 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
const $AssetsImagesGen();
/// File path: assets/images/screenshot_1.png
AssetGenImage get screenshot1 =>
const AssetGenImage('assets/images/screenshot_1.png');
/// File path: assets/images/screenshot_2.png
AssetGenImage get screenshot2 =>
const AssetGenImage('assets/images/screenshot_2.png');
/// File path: assets/images/screenshot_3.png
AssetGenImage get screenshot3 =>
const AssetGenImage('assets/images/screenshot_3.png');
/// File path: assets/images/screenshot_4.png
AssetGenImage get screenshot4 =>
const AssetGenImage('assets/images/screenshot_4.png');
/// List of all assets
List<AssetGenImage> get values =>
[screenshot1, screenshot2, screenshot3, screenshot4];
}
class Assets {
Assets._();
static const $AssetsImagesGen images = $AssetsImagesGen();
}
class AssetGenImage {
const AssetGenImage(this._assetName);
final String _assetName;
Image image({
Key? key,
AssetBundle? bundle,
ImageFrameBuilder? frameBuilder,
ImageErrorWidgetBuilder? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
double? scale,
double? width,
double? height,
Color? color,
Animation<double>? opacity,
BlendMode? colorBlendMode,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Rect? centerSlice,
bool matchTextDirection = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
String? package,
FilterQuality filterQuality = FilterQuality.low,
int? cacheWidth,
int? cacheHeight,
}) {
return Image.asset(
_assetName,
key: key,
bundle: bundle,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
scale: scale,
width: width,
height: height,
color: color,
opacity: opacity,
colorBlendMode: colorBlendMode,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
matchTextDirection: matchTextDirection,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
package: package,
filterQuality: filterQuality,
cacheWidth: cacheWidth,
cacheHeight: cacheHeight,
);
}
ImageProvider provider({
AssetBundle? bundle,
String? package,
}) {
return AssetImage(
_assetName,
bundle: bundle,
package: package,
);
}
String get path => _assetName;
String get keyName => _assetName;
}

@ -0,0 +1,39 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
class FontFamily {
FontFamily._();
/// Font family: SF-UI-Display
static const String sFUIDisplay = 'SF-UI-Display';
/// Font family: ibarra-real-nova
static const String ibarraRealNova = 'ibarra-real-nova';
/// Font family: monospace
static const String monospace = 'monospace';
/// Font family: nunito
static const String nunito = 'nunito';
/// Font family: pacifico
static const String pacifico = 'pacifico';
/// Font family: roboto-mono
static const String robotoMono = 'roboto-mono';
/// Font family: sans-serif
static const String sansSerif = 'sans-serif';
/// Font family: serif
static const String serif = 'serif';
/// Font family: square-peg
static const String squarePeg = 'square-peg';
}

@ -1,33 +1,155 @@
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart'
show
GlobalCupertinoLocalizations,
GlobalMaterialLocalizations,
GlobalWidgetsLocalizations;
import 'package:flutter_quill/flutter_quill.dart' show Document;
import 'package:flutter_quill/translations.dart' show FlutterQuillLocalizations;
import 'package:hydrated_bloc/hydrated_bloc.dart'
show HydratedBloc, HydratedStorage;
import 'package:path_provider/path_provider.dart'
show getApplicationDocumentsDirectory;
import 'pages/home_page.dart';
import 'presentation/home/widgets/home_screen.dart';
import 'presentation/quill/quill_screen.dart';
import 'presentation/quill/samples/quill_default_sample.dart';
import 'presentation/quill/samples/quill_images_sample.dart';
import 'presentation/quill/samples/quill_text_sample.dart';
import 'presentation/quill/samples/quill_videos_sample.dart';
import 'presentation/settings/cubit/settings_cubit.dart';
import 'presentation/settings/widgets/settings_screen.dart';
void main() {
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: kIsWeb
? HydratedStorage.webStorageDirectory
: await getApplicationDocumentsDirectory(),
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => SettingsCubit(),
),
],
child: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
return MaterialApp(
title: 'Flutter Quill Demo',
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
themeMode: state.themeMode,
debugShowCheckedModeBanner: false,
title: 'Quill Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
localizationsDelegates: [
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
// FlutterQuillLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'),
const Locale('zh', 'HK'),
],
home: HomePage(),
supportedLocales: FlutterQuillLocalizations.supportedLocales,
routes: {
SettingsScreen.routeName: (context) => const SettingsScreen(),
},
onGenerateRoute: (settings) {
final name = settings.name;
if (name == HomeScreen.routeName) {
return MaterialPageRoute(
builder: (context) {
return const HomeScreen();
},
);
}
if (name == QuillScreen.routeName) {
return MaterialPageRoute(
builder: (context) {
final args = settings.arguments as QuillScreenArgs;
return QuillScreen(
args: args,
);
},
);
}
return null;
},
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: const Text('Not found'),
),
body: const Text('404'),
),
);
},
home: Builder(
builder: (context) {
final screen = switch (state.defaultScreen) {
DefaultScreen.home => const HomeScreen(),
DefaultScreen.settings => const SettingsScreen(),
DefaultScreen.imagesSample => QuillScreen(
args: QuillScreenArgs(
document: Document.fromJson(quillImagesSample),
),
),
DefaultScreen.videosSample => QuillScreen(
args: QuillScreenArgs(
document: Document.fromJson(quillVideosSample),
),
),
DefaultScreen.textSample => QuillScreen(
args: QuillScreenArgs(
document: Document.fromJson(quillTextSample),
),
),
DefaultScreen.emptySample => QuillScreen(
args: QuillScreenArgs(
document: Document(),
),
),
DefaultScreen.defaultSample => QuillScreen(
args: QuillScreenArgs(
document: Document.fromJson(quillDefaultSample),
),
),
};
return AnimatedSwitcher(
duration: const Duration(milliseconds: 330),
transitionBuilder: (child, animation) {
// This animation is from flutter.dev example
const begin = Offset(0, 1);
const end = Offset.zero;
const curve = Curves.ease;
final tween = Tween(
begin: begin,
end: end,
).chain(
CurveTween(curve: curve),
);
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
child: screen,
);
},
),
);
},
),
);
}
}

@ -1,564 +0,0 @@
// ignore_for_file: avoid_redundant_argument_values
import 'dart:async';
import 'dart:convert';
import 'dart:io' show File, Platform;
import 'dart:ui';
import 'package:file_picker/file_picker.dart';
import 'package:filesystem_picker/filesystem_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import '../universal_ui/universal_ui.dart';
import '../widgets/time_stamp_embed_widget.dart';
import 'read_only_page.dart';
enum _SelectionType {
none,
word,
// line,
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final QuillController _controller;
late final Future<void> _loadDocumentFromAssetsFuture;
final FocusNode _focusNode = FocusNode();
Timer? _selectAllTimer;
_SelectionType _selectionType = _SelectionType.none;
@override
void dispose() {
_selectAllTimer?.cancel();
// Dispose the controller to free resources
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_loadDocumentFromAssetsFuture = _loadFromAssets();
}
Future<void> _loadFromAssets() async {
try {
final result = await rootBundle.loadString(isDesktop()
? 'assets/sample_data_nomedia.json'
: 'assets/sample_data.json');
final doc = Document.fromJson(jsonDecode(result));
_controller = QuillController(
document: doc,
selection: const TextSelection.collapsed(offset: 0),
);
} catch (error) {
final doc = Document()..insert(0, 'Empty asset');
_controller = QuillController(
document: doc,
selection: const TextSelection.collapsed(offset: 0),
);
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _loadDocumentFromAssetsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator.adaptive()),
);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.grey.shade800,
elevation: 0,
centerTitle: false,
title: const Text(
'Flutter Quill',
),
actions: [
IconButton(
onPressed: () => _insertTimeStamp(
_controller,
DateTime.now().toString(),
),
icon: const Icon(Icons.add_alarm_rounded),
),
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(_controller.document.toPlainText([
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
])),
),
),
icon: const Icon(Icons.text_fields_rounded),
)
],
),
drawer: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
color: Colors.grey.shade800,
child: _buildMenuBar(context),
),
body: _buildWelcomeEditor(context),
);
},
);
}
bool _onTripleClickSelection() {
final controller = _controller;
_selectAllTimer?.cancel();
_selectAllTimer = null;
// If you want to select all text after paragraph, uncomment this line
// if (_selectionType == _SelectionType.line) {
// final selection = TextSelection(
// baseOffset: 0,
// extentOffset: controller.document.length,
// );
// controller.updateSelection(selection, ChangeSource.REMOTE);
// _selectionType = _SelectionType.none;
// return true;
// }
if (controller.selection.isCollapsed) {
_selectionType = _SelectionType.none;
}
if (_selectionType == _SelectionType.none) {
_selectionType = _SelectionType.word;
_startTripleClickTimer();
return false;
}
if (_selectionType == _SelectionType.word) {
final child = controller.document.queryChild(
controller.selection.baseOffset,
);
final offset = child.node?.documentOffset ?? 0;
final length = child.node?.length ?? 0;
final selection = TextSelection(
baseOffset: offset,
extentOffset: offset + length,
);
controller.updateSelection(selection, ChangeSource.REMOTE);
// _selectionType = _SelectionType.line;
_selectionType = _SelectionType.none;
_startTripleClickTimer();
return true;
}
return false;
}
void _startTripleClickTimer() {
_selectAllTimer = Timer(const Duration(milliseconds: 900), () {
_selectionType = _SelectionType.none;
});
}
QuillEditor get quillEditor {
if (kIsWeb) {
return QuillEditor(
focusNode: _focusNode,
scrollController: ScrollController(),
configurations: QuillEditorConfigurations(
placeholder: 'Add content',
readOnly: false,
scrollable: true,
autoFocus: false,
expands: false,
padding: EdgeInsets.zero,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
),
embedBuilders: [
...defaultEmbedBuildersWeb,
TimeStampEmbedBuilderWidget()
],
),
);
}
return QuillEditor(
configurations: QuillEditorConfigurations(
placeholder: 'Add content',
readOnly: false,
autoFocus: false,
enableSelectionToolbar: isMobile(),
expands: false,
padding: EdgeInsets.zero,
onImagePaste: _onImagePaste,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
subscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.subscripts()],
),
superscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.superscripts()],
),
),
embedBuilders: [
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
],
),
scrollController: ScrollController(),
focusNode: _focusNode,
);
}
QuillToolbar get quillToolbar {
if (kIsWeb) {
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
),
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus,
);
}
if (_isDesktop()) {
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
),
showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus,
);
}
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
// provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url.
// same goes for videos.
onImagePickCallback: _onImagePickCallback,
onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
),
showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus,
);
}
Widget _buildWelcomeEditor(BuildContext context) {
// BUG in web!! should not releated to this pull request
///
/// EXCEPTION CAUGHT BY WIDGETS LIBRARY
///
// The following bool object was thrown building MediaQuery
//(MediaQueryData(size: Size(769.0, 1205.0),
// devicePixelRatio: 1.0, textScaleFactor: 1.0, platformBrightness:
//Brightness.dark, padding:
// EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets:
// EdgeInsets.zero,
// systemGestureInsets:
// EdgeInsets.zero, alwaysUse24HourFormat: false, accessibleNavigation:
// false,
// highContrast: false,
// disableAnimations: false, invertColors: false, boldText: false,
//navigationMode: traditional,
// gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures:
// []
// )):
// false
// The relevant error-causing widget was:
// SafeArea
///
///
return SafeArea(
child: QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 15,
child: Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16, right: 16),
child: quillEditor,
),
),
kIsWeb
? Expanded(
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: quillToolbar,
))
: Container(
child: quillToolbar,
)
],
),
),
);
}
bool _isDesktop() => !kIsWeb && !Platform.isAndroid && !Platform.isIOS;
Future<String?> openFileSystemPickerForDesktop(BuildContext context) async {
return await FilesystemPicker.open(
context: context,
rootDirectory: await getApplicationDocumentsDirectory(),
fsType: FilesystemType.file,
fileTileSelectMode: FileTileSelectMode.wholeTile,
);
}
// Renders the image picked by imagePicker from local file storage
// You can also upload the picked image to any server (eg : AWS s3
// or Firebase) and then return the uploaded image URL.
Future<String> _onImagePickCallback(File file) async {
// Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile =
await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString();
}
Future<String?> _webImagePickImpl(
OnImagePickCallback onImagePickCallback) async {
final result = await FilePicker.platform.pickFiles();
if (result == null) {
return null;
}
// Take first, because we don't allow picking multiple files.
final fileName = result.files.first.name;
final file = File(fileName);
return onImagePickCallback(file);
}
// Renders the video picked by imagePicker from local file storage
// You can also upload the picked video to any server (eg : AWS s3
// or Firebase) and then return the uploaded video URL.
Future<String> _onVideoPickCallback(File file) async {
// Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile =
await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString();
}
// ignore: unused_element
Future<MediaPickSetting?> _selectMediaPickSetting(BuildContext context) =>
showDialog<MediaPickSetting>(
context: context,
builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton.icon(
icon: const Icon(Icons.collections),
label: const Text('Gallery'),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Gallery),
),
TextButton.icon(
icon: const Icon(Icons.link),
label: const Text('Link'),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Link),
)
],
),
),
);
// ignore: unused_element
Future<MediaPickSetting?> _selectCameraPickSetting(BuildContext context) =>
showDialog<MediaPickSetting>(
context: context,
builder: (ctx) => AlertDialog(
contentPadding: EdgeInsets.zero,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton.icon(
icon: const Icon(Icons.camera),
label: const Text('Capture a photo'),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Camera),
),
TextButton.icon(
icon: const Icon(Icons.video_call),
label: const Text('Capture a video'),
onPressed: () => Navigator.pop(ctx, MediaPickSetting.Video),
)
],
),
),
);
Widget _buildMenuBar(BuildContext context) {
final size = MediaQuery.sizeOf(context);
const itemStyle = TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold,
);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Divider(
thickness: 2,
color: Colors.white,
indent: size.width * 0.1,
endIndent: size.width * 0.1,
),
ListTile(
title: const Center(child: Text('Read only demo', style: itemStyle)),
dense: true,
visualDensity: VisualDensity.compact,
onTap: _readOnly,
),
Divider(
thickness: 2,
color: Colors.white,
indent: size.width * 0.1,
endIndent: size.width * 0.1,
),
],
);
}
void _readOnly() {
Navigator.pop(super.context);
Navigator.push(
super.context,
MaterialPageRoute(
builder: (context) => ReadOnlyPage(),
),
);
}
Future<String> _onImagePaste(Uint8List imageBytes) async {
// Saves the image to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final file = await File(
'${appDocDir.path}/${path.basename('${DateTime.now().millisecondsSinceEpoch}.png')}',
).writeAsBytes(imageBytes, flush: true);
return file.path.toString();
}
static void _insertTimeStamp(QuillController controller, String string) {
controller.document.insert(controller.selection.extentOffset, '\n');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.LOCAL,
);
controller.document.insert(
controller.selection.extentOffset,
TimeStampEmbed(string),
);
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.LOCAL,
);
controller.document.insert(controller.selection.extentOffset, ' ');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.LOCAL,
);
controller.document.insert(controller.selection.extentOffset, '\n');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.LOCAL,
);
}
}

@ -1,81 +0,0 @@
// ignore_for_file: avoid_redundant_argument_values
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import '../universal_ui/universal_ui.dart';
import '../widgets/demo_scaffold.dart';
class ReadOnlyPage extends StatefulWidget {
@override
_ReadOnlyPageState createState() => _ReadOnlyPageState();
}
class _ReadOnlyPageState extends State<ReadOnlyPage> {
final FocusNode _focusNode = FocusNode();
bool _edit = false;
@override
Widget build(BuildContext context) {
return DemoScaffold(
documentFilename: isDesktop()
? 'assets/sample_data_nomedia.json'
: 'sample_data_nomedia.json',
builder: _buildContent,
showToolbar: _edit == true,
floatingActionButton: FloatingActionButton.extended(
label: Text(_edit == true ? 'Done' : 'Edit'),
onPressed: _toggleEdit,
icon: Icon(_edit == true ? Icons.check : Icons.edit),
),
);
}
Widget _buildContent(BuildContext context, QuillController? controller) {
var quillEditor = QuillEditor(
configurations: QuillEditorConfigurations(
expands: false,
padding: EdgeInsets.zero,
embedBuilders: FlutterQuillEmbeds.builders(),
scrollable: true,
autoFocus: true,
),
scrollController: ScrollController(),
focusNode: _focusNode,
// readOnly: !_edit,
);
if (kIsWeb) {
quillEditor = QuillEditor(
configurations: QuillEditorConfigurations(
autoFocus: true,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb,
scrollable: true,
),
scrollController: ScrollController(),
focusNode: _focusNode,
);
}
return Padding(
padding: const EdgeInsets.all(8),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey.shade200),
),
child: quillEditor,
),
);
}
void _toggleEdit() {
setState(() {
_edit = !_edit;
});
}
}

@ -0,0 +1,13 @@
import 'package:flutter/material.dart'
show ScaffoldMessenger, ScaffoldMessengerState, SnackBar;
import 'package:flutter/widgets.dart' show BuildContext, Text;
extension ScaffoldMessengerStateExt on ScaffoldMessengerState {
void showText(String text) {
showSnackBar(SnackBar(content: Text(text)));
}
}
extension BuildContextExt on BuildContext {
ScaffoldMessengerState get messenger => ScaffoldMessenger.of(this);
}

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
class HomeScreenExampleItem extends StatelessWidget {
const HomeScreenExampleItem({
required this.title,
required this.icon,
required this.text,
required this.onPressed,
super.key,
});
final String title;
final Widget icon;
final String text;
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 200,
width: double.infinity,
child: GestureDetector(
onTap: onPressed,
child: Card(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 2),
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
icon,
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.all(8),
child: Text(
text,
style: Theme.of(context).textTheme.bodyMedium,
),
),
],
),
),
),
),
),
],
);
}
}

@ -0,0 +1,188 @@
import 'dart:convert' show jsonDecode;
import 'package:cross_file/cross_file.dart';
import 'package:file_picker/file_picker.dart' show FilePicker, FileType;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import '../../extensions/scaffold_messenger.dart';
import '../../quill/quill_screen.dart';
import '../../quill/samples/quill_default_sample.dart';
import '../../quill/samples/quill_images_sample.dart';
import '../../quill/samples/quill_text_sample.dart';
import '../../quill/samples/quill_videos_sample.dart';
import '../../settings/widgets/settings_screen.dart';
import 'example_item.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
static const routeName = '/home';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Quill Demo'),
),
drawer: Drawer(
child: ListView(
children: [
const DrawerHeader(
child: Text(
'Flutter Quill Demo',
),
),
ListTile(
title: const Text('Settings'),
leading: const Icon(Icons.settings),
onTap: () {
Navigator.of(context)
..pop()
..pushNamed(SettingsScreen.routeName);
},
),
],
),
),
body: SafeArea(
child: Column(
children: [
SizedBox(
width: double.infinity,
child: Text(
'Welcome to Flutter Quill Demo!',
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
),
Expanded(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
HomeScreenExampleItem(
title: 'Default',
icon: const Icon(
Icons.home,
size: 50,
),
text:
'If you want to see how the editor work with default content, '
'see any samples or you are working on it',
onPressed: () => Navigator.of(context).pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document.fromJson(quillDefaultSample),
),
),
),
const SizedBox(height: 4),
HomeScreenExampleItem(
title: 'Images',
icon: const Icon(
Icons.image,
size: 50,
),
text: 'If you want to see how the editor work with images, '
'see any samples or you are working on it',
onPressed: () => Navigator.of(context).pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document.fromJson(quillImagesSample),
),
),
),
const SizedBox(height: 4),
HomeScreenExampleItem(
title: 'Videos',
icon: const Icon(
Icons.video_chat,
size: 50,
),
text: 'If you want to see how the editor work with videos, '
'see any samples or you are working on it',
onPressed: () => Navigator.of(context).pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document.fromJson(quillVideosSample),
),
),
),
HomeScreenExampleItem(
title: 'Text',
icon: const Icon(
Icons.edit_document,
size: 50,
),
text: 'If you want to see how the editor work with text, '
'see any samples or you are working on it',
onPressed: () => Navigator.of(context).pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document.fromJson(quillTextSample),
),
),
),
HomeScreenExampleItem(
title: 'Open a document by delta json',
icon: const Icon(
Icons.file_copy,
size: 50,
),
text: 'If you want to load a document by delta json file',
onPressed: () async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final navigator = Navigator.of(context);
try {
final result = await FilePicker.platform.pickFiles(
dialogTitle: 'Pick json delta',
type: FileType.custom,
allowedExtensions: ['json'],
allowMultiple: false,
);
final file = result?.files.firstOrNull;
final filePath = file?.path;
if (file == null || filePath == null) {
return;
}
final jsonString = await XFile(filePath).readAsString();
navigator.pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document.fromJson(jsonDecode(jsonString)),
),
);
} catch (e) {
print(
'Error while loading json delta file: ${e.toString()}',
);
scaffoldMessenger.showText(
'Error while loading json delta file: ${e.toString()}',
);
}
},
),
HomeScreenExampleItem(
title: 'Empty',
icon: const Icon(
Icons.insert_drive_file,
size: 50,
),
text: 'Want start clean? be my guest',
onPressed: () => Navigator.of(context).pushNamed(
QuillScreen.routeName,
arguments: QuillScreenArgs(
document: Document(),
),
),
),
],
),
),
],
),
),
);
}
}

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' show Icons;
import 'package:flutter/widgets.dart';
import 'package:flutter_quill/flutter_quill.dart';
class TimeStampEmbed extends Embeddable {
@ -21,8 +22,8 @@ class TimeStampEmbedBuilderWidget extends EmbedBuilder {
String get key => 'timeStamp';
@override
String toPlainText(Embed embed) {
return embed.value.data;
String toPlainText(Embed node) {
return node.value.data;
}
@override

@ -0,0 +1,138 @@
import 'dart:io' as io show Directory, File;
import 'dart:ui' show FontFeature;
import 'package:cached_network_image/cached_network_image.dart'
show CachedNetworkImageProvider;
import 'package:desktop_drop/desktop_drop.dart' show DropTarget;
import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart'
show getImageProviderByImageSource, imageFileExtensions;
import 'package:path/path.dart' as path;
import '../extensions/scaffold_messenger.dart';
import 'embeds/timestamp_embed.dart';
class MyQuillEditor extends StatelessWidget {
const MyQuillEditor({
required this.configurations,
required this.scrollController,
required this.focusNode,
super.key,
});
final QuillEditorConfigurations configurations;
final ScrollController scrollController;
final FocusNode focusNode;
@override
Widget build(BuildContext context) {
return QuillEditor(
scrollController: scrollController,
focusNode: focusNode,
configurations: configurations.copyWith(
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null,
),
sizeSmall: TextStyle(fontSize: 9),
subscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.subscripts()],
),
superscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.superscripts()],
),
),
scrollable: true,
placeholder: 'Start writting your notes...',
padding: const EdgeInsets.all(16),
onImagePaste: (imageBytes) async {
if (isWeb()) {
return null;
}
// We will save it to system temporary files
final newFileName = '${DateTime.now().toIso8601String()}.png';
final newPath = path.join(
io.Directory.systemTemp.path,
newFileName,
);
final file = await io.File(
newPath,
).writeAsBytes(imageBytes, flush: true);
return file.path;
},
embedBuilders: [
...(isWeb()
? FlutterQuillEmbeds.editorWebBuilders()
: FlutterQuillEmbeds.editorBuilders(
imageEmbedConfigurations: QuillEditorImageEmbedConfigurations(
imageErrorWidgetBuilder: (context, error, stackTrace) {
return Text(
'Error while loading an image: ${error.toString()}',
);
},
imageProviderBuilder: (imageUrl) {
// cached_network_image is supported
// only for Android, iOS and web
// We will use it only if image from network
if (isAndroid(supportWeb: false) ||
isIOS(supportWeb: false) ||
isWeb()) {
if (isHttpBasedUrl(imageUrl)) {
return CachedNetworkImageProvider(
imageUrl,
);
}
}
return getImageProviderByImageSource(
imageUrl,
imageProviderBuilder: null,
assetsPrefix: QuillSharedExtensionsConfigurations.get(
context: context)
.assetsPrefix,
);
},
),
)),
TimeStampEmbedBuilderWidget(),
],
builder: (context, rawEditor) {
// The `desktop_drop` plugin doesn't support iOS platform for now
if (isIOS(supportWeb: false)) {
return rawEditor;
}
return DropTarget(
onDragDone: (details) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final file = details.files.first;
final isSupported = imageFileExtensions.any(file.name.endsWith);
if (!isSupported) {
scaffoldMessenger.showText(
'Only images are supported right now: ${file.mimeType}, ${file.name}, ${file.path}, $imageFileExtensions',
);
return;
}
context.requireQuillController.insertImageBlock(
imageSource: file.path,
);
scaffoldMessenger.showText('Image is inserted.');
},
child: rawEditor,
);
},
),
);
}
}

@ -0,0 +1,144 @@
import 'dart:convert' show jsonEncode;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'
show FlutterQuillEmbeds, QuillSharedExtensionsConfigurations;
import 'package:quill_html_converter/quill_html_converter.dart';
import 'package:share_plus/share_plus.dart' show Share;
import '../extensions/scaffold_messenger.dart';
import '../shared/widgets/home_screen_button.dart';
import 'quill_editor.dart';
import 'quill_toolbar.dart';
@immutable
class QuillScreenArgs {
const QuillScreenArgs({required this.document});
final Document document;
}
class QuillScreen extends StatefulWidget {
const QuillScreen({
required this.args,
super.key,
});
final QuillScreenArgs args;
static const routeName = '/quill';
@override
State<QuillScreen> createState() => _QuillScreenState();
}
class _QuillScreenState extends State<QuillScreen> {
final _controller = QuillController.basic();
final _editorFocusNode = FocusNode();
final _editorScrollController = ScrollController();
var _isReadOnly = false;
@override
void initState() {
super.initState();
_controller.document = widget.args.document;
}
@override
void dispose() {
_controller.dispose();
_editorFocusNode.dispose();
_editorScrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Quill'),
actions: [
IconButton(
tooltip: 'Load with HTML',
onPressed: () {
final html = _controller.document.toDelta().toHtml();
_controller.document =
Document.fromDelta(DeltaHtmlExt.fromHtml(html));
},
icon: const Icon(Icons.html),
),
IconButton(
tooltip: 'Share',
onPressed: () {
final plainText = _controller.document.toPlainText(
FlutterQuillEmbeds.defaultEditorBuilders(),
);
if (plainText.trim().isEmpty) {
ScaffoldMessenger.of(context).showText(
"We can't share empty document, please enter some text first",
);
return;
}
Share.share(plainText);
},
icon: const Icon(Icons.share),
),
IconButton(
tooltip: 'Print to log',
onPressed: () {
print(
jsonEncode(_controller.document.toDelta().toJson()),
);
ScaffoldMessenger.of(context).showText(
'The quill delta json has been printed to the log.',
);
},
icon: const Icon(Icons.print),
),
const HomeScreenButton(),
],
),
body: QuillProvider(
configurations: QuillConfigurations(
controller: _controller,
sharedConfigurations: QuillSharedConfigurations(
animationConfigurations: QuillAnimationConfigurations.disableAll(),
extraConfigurations: const {
QuillSharedExtensionsConfigurations.key:
QuillSharedExtensionsConfigurations(
assetsPrefix: 'assets',
),
},
),
),
child: Column(
children: [
if (!_isReadOnly)
MyQuillToolbar(
focusNode: _editorFocusNode,
),
Builder(
builder: (context) {
return Expanded(
child: MyQuillEditor(
configurations: QuillEditorConfigurations(
readOnly: _isReadOnly,
),
scrollController: _editorScrollController,
focusNode: _editorFocusNode,
),
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(_isReadOnly ? Icons.lock : Icons.edit),
onPressed: () => setState(() => _isReadOnly = !_isReadOnly),
),
);
}
}

@ -0,0 +1,301 @@
import 'dart:io' as io show File;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'
show getApplicationDocumentsDirectory;
import '../extensions/scaffold_messenger.dart';
import '../settings/cubit/settings_cubit.dart';
import 'embeds/timestamp_embed.dart';
class MyQuillToolbar extends StatelessWidget {
const MyQuillToolbar({
required this.focusNode,
super.key,
});
final FocusNode focusNode;
Future<void> onImageInsertWithCropping(
String image,
QuillController controller,
BuildContext context,
) async {
final croppedFile = await ImageCropper().cropImage(
sourcePath: image,
aspectRatioPresets: [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Cropper',
),
WebUiSettings(
context: context,
),
],
);
final newImage = croppedFile?.path;
if (newImage == null) {
return;
}
if (isWeb()) {
controller.insertImageBlock(imageSource: newImage);
return;
}
final newSavedImage = await saveImage(io.File(newImage));
controller.insertImageBlock(imageSource: newSavedImage);
}
Future<void> onImageInsert(String image, QuillController controller) async {
if (isWeb()) {
controller.insertImageBlock(imageSource: image);
return;
}
final newSavedImage = await saveImage(io.File(image));
controller.insertImageBlock(imageSource: newSavedImage);
}
/// For mobile platforms it will copies the picked file from temporary cache
/// to applications directory
///
/// for desktop platforms, it will do the same but from user files this time
Future<String> saveImage(io.File file) async {
final appDocDir = await getApplicationDocumentsDirectory();
final fileExt = path.extension(file.path);
final newFileName = '${DateTime.now().toIso8601String()}$fileExt';
final newPath = path.join(
appDocDir.path,
newFileName,
);
final copiedFile = await file.copy(newPath);
return copiedFile.path;
}
@override
Widget build(BuildContext context) {
return BlocBuilder<SettingsCubit, SettingsState>(
buildWhen: (previous, current) =>
previous.useCustomQuillToolbar != current.useCustomQuillToolbar,
builder: (context, state) {
if (state.useCustomQuillToolbar) {
// For more info
// https://github.com/singerdmx/flutter-quill/blob/master/doc/custom_toolbar.md
return QuillToolbarProvider(
toolbarConfigurations: const QuillToolbarConfigurations(),
child: QuillBaseToolbar(
configurations: QuillBaseToolbarConfigurations(
toolbarSize: 15 * 2,
multiRowsDisplay: false,
childrenBuilder: (context) {
final controller = context.requireQuillController;
return [
QuillToolbarImageButton(
controller: controller,
options: const QuillToolbarImageButtonOptions(),
),
QuillToolbarHistoryButton(
controller: controller,
options:
const QuillToolbarHistoryButtonOptions(isUndo: true),
),
QuillToolbarHistoryButton(
controller: controller,
options:
const QuillToolbarHistoryButtonOptions(isUndo: false),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_bold,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_italic,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_underline,
iconSize: 20,
),
),
QuillToolbarClearFormatButton(
controller: controller,
options: const QuillToolbarClearFormatButtonOptions(
iconData: Icons.format_clear,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options:
const QuillToolbarSelectHeaderStyleButtonsOptions(
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_numbered,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_list_bulleted,
iconSize: 20,
),
),
QuillToolbarToggleStyleButton(
attribute: Attribute.blockQuote,
controller: controller,
options: const QuillToolbarToggleStyleButtonOptions(
iconData: Icons.format_quote,
iconSize: 20,
),
),
VerticalDivider(
indent: 12,
endIndent: 12,
color: Colors.grey.shade400,
),
QuillToolbarIndentButton(
controller: controller,
isIncrease: true,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_increase,
iconSize: 20,
)),
QuillToolbarIndentButton(
controller: controller,
isIncrease: false,
options: const QuillToolbarIndentButtonOptions(
iconData: Icons.format_indent_decrease,
iconSize: 20,
),
),
];
},
),
),
);
}
return QuillToolbar(
configurations: QuillToolbarConfigurations(
showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
// Request editor focus when any button is pressed
afterButtonPressed: focusNode.requestFocus,
),
),
customButtons: [
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.add_alarm_rounded),
onPressed: () {
final controller = context.requireQuillController;
controller.document
.insert(controller.selection.extentOffset, '\n');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.local,
);
controller.document.insert(
controller.selection.extentOffset,
TimeStampEmbed(
DateTime.now().toString(),
),
);
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.local,
);
controller.document
.insert(controller.selection.extentOffset, ' ');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.local,
);
controller.document
.insert(controller.selection.extentOffset, '\n');
controller.updateSelection(
TextSelection.collapsed(
offset: controller.selection.extentOffset + 1,
),
ChangeSource.local,
);
},
),
QuillToolbarCustomButtonOptions(
icon: const Icon(Icons.ac_unit),
onPressed: () {
ScaffoldMessenger.of(context)
..clearSnackBars()
..showText(
'Custom button!',
);
},
),
],
embedButtons: FlutterQuillEmbeds.toolbarButtons(
imageButtonOptions: QuillToolbarImageButtonOptions(
imageButtonConfigurations: QuillToolbarImageConfigurations(
onImageInsertCallback: isAndroid(supportWeb: false) ||
isIOS(supportWeb: false) ||
isWeb()
? (image, controller) =>
onImageInsertWithCropping(image, controller, context)
: onImageInsert,
),
),
),
),
);
},
);
}
}

@ -0,0 +1,295 @@
import '../../../gen/assets.gen.dart';
final quillDefaultSample = [
{
'insert': {'image': Assets.images.screenshot1.path},
'attributes': {
'width': '100',
'height': '100',
'style': 'width:500px; height:350px;'
}
},
{'insert': 'Flutter Quill'},
{
'attributes': {'header': 1},
'insert': '\n'
},
{
'insert': {
'video':
'https://www.youtube.com/watch?v=V4hgdKhIqtc&list=PLbhaS_83B97s78HsDTtplRTEhcFsqSqIK&index=1'
}
},
{
'insert': {
'video':
'https://user-images.githubusercontent.com/122956/126238875-22e42501-ad41-4266-b1d6-3f89b5e3b79b.mp4'
}
},
{'insert': '\nRich text editor for Flutter'},
{
'attributes': {'header': 2},
'insert': '\n'
},
{'insert': 'Quill component for Flutter'},
{
'attributes': {'header': 3},
'insert': '\n'
},
{
'attributes': {'link': 'https://bulletjournal.us/home/index.html'},
'insert': 'Bullet Journal'
},
{
'insert':
':\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders'
},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{
'insert':
'Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices'
},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Check out what you and your teammates are working on each day'},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': '\nSplitting bills with friends can never be easier.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Start creating a group and invite your friends to join.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Create a BuJo of Ledger type to see expense or balance summary.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{
'insert':
'\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s).'
},
{
'attributes': {'blockquote': true},
'insert': '\n'
},
{'insert': "\nvar BuJo = 'Bullet' + 'Journal'"},
{
'attributes': {'code-block': true},
'insert': '\n'
},
{'insert': '\nStart tracking in your browser'},
{
'attributes': {'indent': 1},
'insert': '\n'
},
{'insert': 'Stop the timer on your phone'},
{
'attributes': {'indent': 1},
'insert': '\n'
},
{'insert': 'All your time entries are synced'},
{
'attributes': {'indent': 2},
'insert': '\n'
},
{'insert': 'between the phone apps'},
{
'attributes': {'indent': 2},
'insert': '\n'
},
{'insert': 'and the website.'},
{
'attributes': {'indent': 3},
'insert': '\n'
},
{'insert': '\n'},
{'insert': '\nCenter Align'},
{
'attributes': {'align': 'center'},
'insert': '\n'
},
{'insert': 'Right Align'},
{
'attributes': {'align': 'right'},
'insert': '\n'
},
{'insert': 'Justify Align'},
{
'attributes': {'align': 'justify'},
'insert': '\n'
},
{'insert': 'Have trouble finding things? '},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Just type in the search bar'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'and easily find contents'},
{
'attributes': {'indent': 2, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'across projects or folders.'},
{
'attributes': {'indent': 2, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'It matches text in your note or task.'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Enable reminders so that you will get notified by'},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'email'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'message on your phone'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'popup on the web site'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Create a BuJo serving as project or folder'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Organize your'},
{
'attributes': {'indent': 1, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'tasks'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'notes'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'transactions'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'under BuJo '},
{
'attributes': {'indent': 3, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'See them in Calendar'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'or hierarchical view'},
{
'attributes': {'indent': 1, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'this is a check list'},
{
'attributes': {'list': 'checked'},
'insert': '\n'
},
{'insert': 'this is a uncheck list'},
{
'attributes': {'list': 'unchecked'},
'insert': '\n'
},
{'insert': 'Font '},
{
'attributes': {'font': 'sans-serif'},
'insert': 'Sans Serif'
},
{'insert': ' '},
{
'attributes': {'font': 'serif'},
'insert': 'Serif'
},
{'insert': ' '},
{
'attributes': {'font': 'monospace'},
'insert': 'Monospace'
},
{'insert': ' Size '},
{
'attributes': {'size': 'small'},
'insert': 'Small'
},
{'insert': ' '},
{
'attributes': {'size': 'large'},
'insert': 'Large'
},
{'insert': ' '},
{
'attributes': {'size': 'huge'},
'insert': 'Huge'
},
{
'attributes': {'size': '15.0'},
'insert': 'font size 15'
},
{'insert': ' '},
{
'attributes': {'size': '35'},
'insert': 'font size 35'
},
{'insert': ' '},
{
'attributes': {'size': '20'},
'insert': 'font size 20'
},
{
'attributes': {'token': 'built_in'},
'insert': ' diff'
},
{
'attributes': {'token': 'operator'},
'insert': '-match'
},
{
'attributes': {'token': 'literal'},
'insert': '-patch'
},
{
'insert': {
'image':
'https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png'
},
'attributes': {
'width': '230',
'style': 'display: block; margin: auto; width: 500px;'
}
},
{'insert': '\n'}
];

File diff suppressed because one or more lines are too long

@ -0,0 +1,270 @@
const quillTextSample = [
{'insert': 'Flutter Quill'},
{
'attributes': {'header': 1},
'insert': '\n'
},
{'insert': '\nRich text editor for Flutter'},
{
'attributes': {'header': 2},
'insert': '\n'
},
{'insert': 'Quill component for Flutter'},
{
'attributes': {'color': 'rgba(0, 0, 0, 0.847)'},
'insert': ' and '
},
{
'attributes': {'link': 'https://bulletjournal.us/home/index.html'},
'insert': 'Bullet Journal'
},
{
'insert':
':\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders'
},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{
'insert':
'Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices'
},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Check out what you and your teammates are working on each day'},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': '\nSplitting bills with friends can never be easier.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Start creating a group and invite your friends to join.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Create a BuJo of Ledger type to see expense or balance summary.'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{
'insert':
'\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s).'
},
{
'attributes': {'blockquote': true},
'insert': '\n'
},
{'insert': "\nvar BuJo = 'Bullet' + 'Journal'"},
{
'attributes': {'code-block': true},
'insert': '\n'
},
{'insert': '\nStart tracking in your browser'},
{
'attributes': {'indent': 1},
'insert': '\n'
},
{'insert': 'Stop the timer on your phone'},
{
'attributes': {'indent': 1},
'insert': '\n'
},
{'insert': 'All your time entries are synced'},
{
'attributes': {'indent': 2},
'insert': '\n'
},
{'insert': 'between the phone apps'},
{
'attributes': {'indent': 2},
'insert': '\n'
},
{'insert': 'and the website.'},
{
'attributes': {'indent': 3},
'insert': '\n'
},
{'insert': '\n'},
{'insert': '\nCenter Align'},
{
'attributes': {'align': 'center'},
'insert': '\n'
},
{'insert': 'Right Align'},
{
'attributes': {'align': 'right'},
'insert': '\n'
},
{'insert': 'Justify Align'},
{
'attributes': {'align': 'justify'},
'insert': '\n'
},
{'insert': 'Have trouble finding things? '},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Just type in the search bar'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'and easily find contents'},
{
'attributes': {'indent': 2, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'across projects or folders.'},
{
'attributes': {'indent': 2, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'It matches text in your note or task.'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Enable reminders so that you will get notified by'},
{
'attributes': {'list': 'ordered'},
'insert': '\n'
},
{'insert': 'email'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'message on your phone'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'popup on the web site'},
{
'attributes': {'indent': 1, 'list': 'ordered'},
'insert': '\n'
},
{'insert': 'Create a BuJo serving as project or folder'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'Organize your'},
{
'attributes': {'indent': 1, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'tasks'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'notes'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'transactions'},
{
'attributes': {'indent': 2, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'under BuJo '},
{
'attributes': {'indent': 3, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'See them in Calendar'},
{
'attributes': {'list': 'bullet'},
'insert': '\n'
},
{'insert': 'or hierarchical view'},
{
'attributes': {'indent': 1, 'list': 'bullet'},
'insert': '\n'
},
{'insert': 'this is a check list'},
{
'attributes': {'list': 'checked'},
'insert': '\n'
},
{'insert': 'this is a uncheck list'},
{
'attributes': {'list': 'unchecked'},
'insert': '\n'
},
{'insert': 'Font '},
{
'attributes': {'font': 'sans-serif'},
'insert': 'Sans Serif'
},
{'insert': ' '},
{
'attributes': {'font': 'serif'},
'insert': 'Serif'
},
{'insert': ' '},
{
'attributes': {'font': 'monospace'},
'insert': 'Monospace'
},
{'insert': ' Size '},
{
'attributes': {'size': 'small'},
'insert': 'Small'
},
{'insert': ' '},
{
'attributes': {'size': 'large'},
'insert': 'Large'
},
{'insert': ' '},
{
'attributes': {'size': 'huge'},
'insert': 'Huge'
},
{
'attributes': {'size': '15.0'},
'insert': 'font size 15'
},
{'insert': ' '},
{
'attributes': {'size': '35'},
'insert': 'font size 35'
},
{'insert': ' '},
{
'attributes': {'size': '20'},
'insert': 'font size 20'
},
{
'attributes': {'token': 'built_in'},
'insert': ' diff'
},
{
'attributes': {'token': 'operator'},
'insert': '-match'
},
{
'attributes': {'token': 'literal'},
'insert': '-patch'
},
{
'insert': {
'image':
'https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png'
},
'attributes': {'width': '230', 'style': 'display: block; margin: auto;'}
},
{'insert': '\n'}
];

@ -0,0 +1,19 @@
const quillVideosSample = [
{'insert': '\n'},
{
'insert': {'video': 'https://youtu.be/xz6_AlJkDPA'},
'attributes': {
'width': '300',
'height': '300',
'style': 'width:400px; height:500px;'
}
},
{'insert': '\n'},
{'insert': '\n'},
{'insert': 'And this is just a youtube video'},
{'insert': '\n'},
{
'insert': 'This sample is not complete.',
},
{'insert': '\n'},
];

@ -0,0 +1,26 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart' show ThemeMode;
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart' show HydratedMixin;
part 'settings_state.dart';
part 'settings_cubit.freezed.dart';
part 'settings_cubit.g.dart';
class SettingsCubit extends Cubit<SettingsState> with HydratedMixin {
SettingsCubit() : super(const SettingsState());
void updateSettings(SettingsState newSettingsState) {
emit(newSettingsState);
}
@override
SettingsState? fromJson(Map<String, dynamic> json) {
return SettingsState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(SettingsState state) {
return state.toJson();
}
}

@ -0,0 +1,202 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'settings_cubit.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
SettingsState _$SettingsStateFromJson(Map<String, dynamic> json) {
return _SettingsState.fromJson(json);
}
/// @nodoc
mixin _$SettingsState {
ThemeMode get themeMode => throw _privateConstructorUsedError;
DefaultScreen get defaultScreen => throw _privateConstructorUsedError;
bool get useCustomQuillToolbar => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$SettingsStateCopyWith<SettingsState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SettingsStateCopyWith<$Res> {
factory $SettingsStateCopyWith(
SettingsState value, $Res Function(SettingsState) then) =
_$SettingsStateCopyWithImpl<$Res, SettingsState>;
@useResult
$Res call(
{ThemeMode themeMode,
DefaultScreen defaultScreen,
bool useCustomQuillToolbar});
}
/// @nodoc
class _$SettingsStateCopyWithImpl<$Res, $Val extends SettingsState>
implements $SettingsStateCopyWith<$Res> {
_$SettingsStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? themeMode = null,
Object? defaultScreen = null,
Object? useCustomQuillToolbar = null,
}) {
return _then(_value.copyWith(
themeMode: null == themeMode
? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode,
defaultScreen: null == defaultScreen
? _value.defaultScreen
: defaultScreen // ignore: cast_nullable_to_non_nullable
as DefaultScreen,
useCustomQuillToolbar: null == useCustomQuillToolbar
? _value.useCustomQuillToolbar
: useCustomQuillToolbar // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$SettingsStateImplCopyWith<$Res>
implements $SettingsStateCopyWith<$Res> {
factory _$$SettingsStateImplCopyWith(
_$SettingsStateImpl value, $Res Function(_$SettingsStateImpl) then) =
__$$SettingsStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{ThemeMode themeMode,
DefaultScreen defaultScreen,
bool useCustomQuillToolbar});
}
/// @nodoc
class __$$SettingsStateImplCopyWithImpl<$Res>
extends _$SettingsStateCopyWithImpl<$Res, _$SettingsStateImpl>
implements _$$SettingsStateImplCopyWith<$Res> {
__$$SettingsStateImplCopyWithImpl(
_$SettingsStateImpl _value, $Res Function(_$SettingsStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? themeMode = null,
Object? defaultScreen = null,
Object? useCustomQuillToolbar = null,
}) {
return _then(_$SettingsStateImpl(
themeMode: null == themeMode
? _value.themeMode
: themeMode // ignore: cast_nullable_to_non_nullable
as ThemeMode,
defaultScreen: null == defaultScreen
? _value.defaultScreen
: defaultScreen // ignore: cast_nullable_to_non_nullable
as DefaultScreen,
useCustomQuillToolbar: null == useCustomQuillToolbar
? _value.useCustomQuillToolbar
: useCustomQuillToolbar // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SettingsStateImpl implements _SettingsState {
const _$SettingsStateImpl(
{this.themeMode = ThemeMode.system,
this.defaultScreen = DefaultScreen.home,
this.useCustomQuillToolbar = false});
factory _$SettingsStateImpl.fromJson(Map<String, dynamic> json) =>
_$$SettingsStateImplFromJson(json);
@override
@JsonKey()
final ThemeMode themeMode;
@override
@JsonKey()
final DefaultScreen defaultScreen;
@override
@JsonKey()
final bool useCustomQuillToolbar;
@override
String toString() {
return 'SettingsState(themeMode: $themeMode, defaultScreen: $defaultScreen, useCustomQuillToolbar: $useCustomQuillToolbar)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SettingsStateImpl &&
(identical(other.themeMode, themeMode) ||
other.themeMode == themeMode) &&
(identical(other.defaultScreen, defaultScreen) ||
other.defaultScreen == defaultScreen) &&
(identical(other.useCustomQuillToolbar, useCustomQuillToolbar) ||
other.useCustomQuillToolbar == useCustomQuillToolbar));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, themeMode, defaultScreen, useCustomQuillToolbar);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
__$$SettingsStateImplCopyWithImpl<_$SettingsStateImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SettingsStateImplToJson(
this,
);
}
}
abstract class _SettingsState implements SettingsState {
const factory _SettingsState(
{final ThemeMode themeMode,
final DefaultScreen defaultScreen,
final bool useCustomQuillToolbar}) = _$SettingsStateImpl;
factory _SettingsState.fromJson(Map<String, dynamic> json) =
_$SettingsStateImpl.fromJson;
@override
ThemeMode get themeMode;
@override
DefaultScreen get defaultScreen;
@override
bool get useCustomQuillToolbar;
@override
@JsonKey(ignore: true)
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

@ -0,0 +1,40 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings_cubit.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SettingsStateImpl _$$SettingsStateImplFromJson(Map<String, dynamic> json) =>
_$SettingsStateImpl(
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ??
ThemeMode.system,
defaultScreen:
$enumDecodeNullable(_$DefaultScreenEnumMap, json['defaultScreen']) ??
DefaultScreen.home,
useCustomQuillToolbar: json['useCustomQuillToolbar'] as bool? ?? false,
);
Map<String, dynamic> _$$SettingsStateImplToJson(_$SettingsStateImpl instance) =>
<String, dynamic>{
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!,
'defaultScreen': _$DefaultScreenEnumMap[instance.defaultScreen]!,
'useCustomQuillToolbar': instance.useCustomQuillToolbar,
};
const _$ThemeModeEnumMap = {
ThemeMode.system: 'system',
ThemeMode.light: 'light',
ThemeMode.dark: 'dark',
};
const _$DefaultScreenEnumMap = {
DefaultScreen.home: 'home',
DefaultScreen.settings: 'settings',
DefaultScreen.defaultSample: 'defaultSample',
DefaultScreen.imagesSample: 'imagesSample',
DefaultScreen.videosSample: 'videosSample',
DefaultScreen.textSample: 'textSample',
DefaultScreen.emptySample: 'emptySample',
};

@ -0,0 +1,22 @@
part of 'settings_cubit.dart';
enum DefaultScreen {
home,
settings,
defaultSample,
imagesSample,
videosSample,
textSample,
emptySample,
}
@freezed
class SettingsState with _$SettingsState {
const factory SettingsState({
@Default(ThemeMode.system) ThemeMode themeMode,
@Default(DefaultScreen.home) DefaultScreen defaultScreen,
@Default(false) bool useCustomQuillToolbar,
}) = _SettingsState;
factory SettingsState.fromJson(Map<String, Object?> json) =>
_$SettingsStateFromJson(json);
}

@ -0,0 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../shared/widgets/dialog_action.dart';
import '../../shared/widgets/home_screen_button.dart';
import '../cubit/settings_cubit.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
static const routeName = '/settings';
@override
Widget build(BuildContext context) {
final materialTheme = Theme.of(context);
final isDark = materialTheme.brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
title: const Text('Settings'),
actions: const [
HomeScreenButton(),
],
),
body: BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
return ListView(
children: [
CheckboxListTile.adaptive(
value: isDark,
onChanged: (value) {
final isNewValueDark = value ?? false;
context.read<SettingsCubit>().updateSettings(
state.copyWith(
themeMode:
isNewValueDark ? ThemeMode.dark : ThemeMode.light,
),
);
},
title: const Text('Dark Theme'),
subtitle: const Text(
'By default we will use your system theme, but you can set if you want dark or light theme',
),
secondary: Icon(isDark ? Icons.nightlight : Icons.sunny),
),
ListTile(
title: const Text('Default screen'),
subtitle: const Text(
'Which screen should be used when the flutter app starts?',
),
leading: const Icon(Icons.home),
onTap: () async {
final settingsBloc = context.read<SettingsCubit>();
final newDefaultScreen =
await showAdaptiveDialog<DefaultScreen>(
context: context,
builder: (context) {
return AlertDialog.adaptive(
title: const Text('Select default screen'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
...DefaultScreen.values.map(
(e) => Material(
child: ListTile(
onTap: () {
Navigator.of(context).pop(e);
},
title: Text(e.name),
leading: CircleAvatar(
child: Text((e.index + 1).toString()),
),
),
),
),
],
),
actions: [
AppDialogAction(
onPressed: () => Navigator.of(context).pop(null),
options: const DialogActionOptions(
cupertinoDialogActionOptions:
CupertinoDialogActionOptions(
isDefaultAction: true,
),
),
child: const Text('Cancel'),
),
],
);
},
);
if (newDefaultScreen != null) {
settingsBloc.updateSettings(
settingsBloc.state
.copyWith(defaultScreen: newDefaultScreen),
);
}
},
),
CheckboxListTile.adaptive(
value: state.useCustomQuillToolbar,
onChanged: (value) {
final useCustomToolbarNewValue = value ?? false;
context.read<SettingsCubit>().updateSettings(
state.copyWith(
useCustomQuillToolbar: useCustomToolbarNewValue,
),
);
},
title: const Text('Use custom Quill toolbar'),
subtitle: const Text(
'By default we will default QuillToolbar, but you can decide if you the built-in or the custom one',
),
secondary: const Icon(Icons.dashboard_customize),
),
],
);
},
),
);
}
}

@ -0,0 +1,63 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart';
@immutable
final class CupertinoDialogActionOptions {
const CupertinoDialogActionOptions({
this.isDefaultAction = false,
});
final bool isDefaultAction;
}
@immutable
final class MaterialDialogActionOptions {
const MaterialDialogActionOptions({
this.textStyle,
});
final ButtonStyle? textStyle;
}
@immutable
class DialogActionOptions {
const DialogActionOptions({
this.cupertinoDialogActionOptions,
this.materialDialogActionOptions,
});
final CupertinoDialogActionOptions? cupertinoDialogActionOptions;
final MaterialDialogActionOptions? materialDialogActionOptions;
}
class AppDialogAction extends StatelessWidget {
const AppDialogAction({
required this.child,
required this.onPressed,
this.options,
super.key,
});
final VoidCallback? onPressed;
final Widget child;
final DialogActionOptions? options;
@override
Widget build(BuildContext context) {
if (isAppleOS(supportWeb: true)) {
return CupertinoDialogAction(
onPressed: onPressed,
isDefaultAction:
options?.cupertinoDialogActionOptions?.isDefaultAction ?? false,
child: child,
);
}
return TextButton(
onPressed: onPressed,
style: options?.materialDialogActionOptions?.textStyle,
child: child,
);
}
}

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../settings/cubit/settings_cubit.dart';
class HomeScreenButton extends StatelessWidget {
const HomeScreenButton({super.key});
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () {
final settingsCubit = context.read<SettingsCubit>();
settingsCubit.updateSettings(
settingsCubit.state.copyWith(
defaultScreen: DefaultScreen.home,
),
);
},
icon: const Icon(Icons.home),
tooltip: 'Set the default to home screen',
);
}
}

@ -1,3 +0,0 @@
class PlatformViewRegistry {
static void registerViewFactory(String viewId, dynamic cb) {}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save