├── .metadata ├── lib ├── highlight_model.dart └── highlightable.dart ├── pubspec.yaml ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── tests.yml │ ├── coverage.yml │ └── doctor.yml ├── LICENSE ├── test └── highlightable_test.dart ├── .gitignore ├── example └── main.dart ├── README.md ├── CONTRIBUTING.md ├── CHANGELOG.md └── pubspec.lock /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /lib/highlight_model.dart: -------------------------------------------------------------------------------- 1 | /// [Highlight] is the custom highlighting object 2 | /// Used as [HighlightText]'s highlighting data. 3 | /// 4 | /// [pattern] or [words], one of them must be provided. 5 | /// [pattern] would be used as [RegExp] model's pattern to check if data has match. 6 | /// [words]'s each element would be compared to data. 7 | class Highlight { 8 | final String? pattern; 9 | final List? words; 10 | 11 | const Highlight({this.pattern, this.words}); 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: highlightable 2 | description: A text widget alternative, that highligts defined chars (from pattern / pure-string) 3 | version: 1.0.5 4 | homepage: https://github.com/theiskaa/highlightable 5 | issue_tracker: https://github.com/theiskaa/highlightable/issues 6 | documentation: https://github.com/theiskaa/highlightable#readme 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | flutter: ">=1.17.0" 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | flutter: 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | # This workflow is triggered on pull requests and pushes to the repository. 4 | on: 5 | pull_request: 6 | branches: [beta, develop, master] 7 | push: 8 | branches: [beta, develop, master] 9 | 10 | jobs: 11 | test: 12 | name: Test the application 13 | 14 | # This job will run on Ubuntu virtual machine. 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - name: Checkout the repository 18 | uses: actions/checkout@v2 19 | 20 | # Setup the Flutter environment. 21 | - uses: subosito/flutter-action@v1 22 | with: 23 | channel: "stable" # "alpha", "beta", "dev", default to: "stable" 24 | # 25 | # # You can also specify exact version of Flutter. 26 | flutter-version: "2.2.0" 27 | 28 | - name: Test the application 29 | run: flutter test 30 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | # This workflow is triggered on pull requests and pushes to the repository. 4 | on: 5 | pull_request: 6 | branches: [beta, develop, master] 7 | push: 8 | branches: [beta, develop, master] 9 | 10 | jobs: 11 | coverage: 12 | name: Upload code coverage to Codecov 13 | 14 | # This job will run on Ubuntu virtual machine. 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - name: Checkout the repository 18 | uses: actions/checkout@v2 19 | 20 | # Setup the Flutter environment. 21 | - uses: subosito/flutter-action@v1 22 | with: 23 | channel: "stable" 24 | flutter-version: "2.2.0" 25 | 26 | - name: Test the application 27 | run: flutter test --coverage 28 | 29 | - name: Code Coverage 30 | uses: codecov/codecov-action@v1.2.1 31 | with: 32 | token: ${{ secrets.CODECOV_TOKEN }} 33 | file: coverage/lcov.info -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ismael Shakverdiev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/doctor.yml: -------------------------------------------------------------------------------- 1 | name: doctor 2 | 3 | # This workflow is triggered on pull requests and pushes to the repository. 4 | on: 5 | pull_request: 6 | branches: [beta, develop, master] 7 | push: 8 | branches: [beta, develop, master] 9 | 10 | jobs: 11 | doctor: 12 | name: Check health, format and analyze the application 13 | 14 | # This job will run on Ubuntu virtual machine. 15 | runs-on: ubuntu-20.04 16 | steps: 17 | - name: Checkout the repository 18 | uses: actions/checkout@v2 19 | 20 | # Setup the Flutter environment. 21 | - uses: subosito/flutter-action@v1 22 | with: 23 | channel: "stable" # "alpha", "beta", "dev", default to: "stable" 24 | # 25 | # # You can also specify exact version of Flutter. 26 | flutter-version: "2.2.0" 27 | 28 | - name: Print Flutter SDK version 29 | run: flutter --version 30 | 31 | - name: Print Flutter health information 32 | run: flutter doctor 33 | 34 | # Consider passing '--output=none' to 'format' for no standard output. 35 | - name: Verify formatting 36 | run: flutter format --set-exit-if-changed . 37 | 38 | # Consider passing '--fatal-infos' for slightly stricter analysis. 39 | - name: Statically analyze the Dart code for any errors 40 | run: flutter analyze 41 | -------------------------------------------------------------------------------- /test/highlightable_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:highlightable/highlightable.dart'; 4 | 5 | void main() { 6 | late MaterialApp testApp, testApp2; 7 | late HighlightText highlightText, highlightText2; 8 | 9 | setUpAll(() { 10 | highlightText = HighlightText( 11 | 'Hello Tests', 12 | highlight: Highlight(), 13 | detectWords: true, 14 | ); 15 | 16 | highlightText2 = HighlightText( 17 | 'Numbers: 20, 30, 40 will be highlighted', 18 | highlight: Highlight(pattern: r'\d', words: ["highlighted"]), 19 | detectWords: true, 20 | style: TextStyle(fontSize: 5), 21 | ); 22 | 23 | testApp = MaterialApp(home: Scaffold(body: highlightText)); 24 | testApp2 = MaterialApp(home: Scaffold(body: highlightText2)); 25 | }); 26 | 27 | group('[HighlightText]', () { 28 | testWidgets( 29 | 'should expect Text widget when there is no highlighted data', 30 | (WidgetTester tester) async { 31 | await tester.pumpWidget(testApp); 32 | 33 | expect(find.byType(Text), findsOneWidget); 34 | }, 35 | ); 36 | 37 | testWidgets( 38 | 'should expect generated text spans appropriate to highlight matchers', 39 | (WidgetTester tester) async { 40 | await tester.pumpWidget(testApp2); 41 | 42 | expect(find.byType(RichText), findsOneWidget); 43 | }, 44 | ); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | /coverage 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | build/ 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Flutter.podspec 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/ephemeral 66 | **/ios/Flutter/app.flx 67 | **/ios/Flutter/app.zip 68 | **/ios/Flutter/flutter_assets/ 69 | **/ios/Flutter/flutter_export_environment.sh 70 | **/ios/ServiceDefinitions.json 71 | **/ios/Runner/GeneratedPluginRegistrant.* 72 | 73 | # Exceptions to above rules. 74 | !**/ios/**/default.mode1v3 75 | !**/ios/**/default.mode2v3 76 | !**/ios/**/default.pbxuser 77 | !**/ios/**/default.perspectivev3 78 | -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:highlightable/highlightable.dart'; 3 | 4 | void main() => runApp(const App()); 5 | 6 | class App extends StatelessWidget { 7 | const App({Key? key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) => const MaterialApp(home: Home()); 11 | } 12 | 13 | class Home extends StatelessWidget { 14 | const Home({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar(title: const Text("Highlightable Examples")), 20 | body: Center( 21 | child: Padding( 22 | padding: const EdgeInsets.all(15), 23 | child: Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | // Basic usage. 27 | HighlightText( 28 | 'Only numbers: [1, 25, 50, ...] will be highlighted', 29 | // would highlight only numbers. 30 | highlight: const Highlight(pattern: r'\d'), 31 | ), 32 | 33 | const SizedBox(height: 50), 34 | 35 | // Custom Usage 36 | HighlightText( 37 | "Hello, Flutter!", 38 | // Would highlight only "Flutter" 39 | // full word 'cause [detectWords] is enabled. 40 | highlight: const Highlight( 41 | words: ["Flutter"], 42 | ), 43 | caseSensitive: true, // Turn on case-sensitive. 44 | detectWords: true, 45 | style: const TextStyle( 46 | fontSize: 25, 47 | color: Colors.black, 48 | fontWeight: FontWeight.bold, 49 | ), 50 | highlightStyle: const TextStyle( 51 | fontSize: 25, 52 | letterSpacing: 2.5, 53 | color: Colors.white, 54 | backgroundColor: Colors.blue, 55 | fontWeight: FontWeight.bold, 56 | ), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | Package Logo 4 |
5 |
6 | 7 | License: MIT 8 | 9 | 10 | License: MIT 11 | 12 | 13 | CONTRIBUTING 14 | 15 | 16 |

17 | 18 | ## Installing 19 | - See the official installing guideline from [highlightable/install](https://pub.dev/packages/highlightable/install) 20 | 21 | ## Usage 22 | 23 | ### _Basic usage:_ 24 | ```dart 25 | HighlightText( 26 | 'Only numbers: [1, 25, 50, ...] will be highlighted', 27 | // would highlight only numbers. 28 | highlight: const Highlight(pattern: r'\d'), 29 | ) 30 | ``` 31 | 32 | basic-usage 33 | 34 | ### _Custom usage:_ 35 | ```dart 36 | HighlightText( 37 | "Hello, Flutter!", 38 | // Would highlight only the "Flutter" full word 'cause [detectWords] is enabled. 39 | highlight: const Highlight( 40 | words: ["Flutter"], 41 | ), 42 | caseSensitive: true, // Turn on case-sensitive. 43 | detectWords: true, // Turn on full-word-detection. 44 | style: const TextStyle( 45 | fontSize: 25, 46 | color: Colors.black, 47 | fontWeight: FontWeight.bold, 48 | ), 49 | highlightStyle: const TextStyle( 50 | fontSize: 25, 51 | letterSpacing: 2.5, 52 | color: Colors.white, 53 | backgroundColor: Colors.blue, 54 | fontWeight: FontWeight.bold, 55 | ), 56 | ) 57 | ``` 58 | custom-usage 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | First of all, thank you for considering contributing! :tada: 4 | 5 | Here you will find some guidelines on how to contribute. Feel free to propose changes to this document if you think something is missing or needs to be clarified. 6 | 7 | ## Contributing Guides 8 | 9 | First time contributing? You can learn more about contributing to open source projects through GitHub's [wonderful open source guides](https://opensource.guide/how-to-contribute/). 10 | 11 | ### :bug: Reporting a Problem 12 | 13 | Before you open a new issue, check to see if the problem has [already been reported](https://github.com/theiskaa/highlightable-text/issues). If it has and **the issue is still open**, add a comment to the existing issue instead of creating a new one. 14 | 15 | > **Note:** If you find a **closed** issue that seems to address the same thing that you've found, open a new issue and include a link to the original one. 16 | 17 | When you open an issue, try to be as descriptive as possible. Add any relevant screenshots so that the problem can be identified quickly. 18 | 19 | ### :sparkles: Opening a Pull Request 20 | 21 | > A lot of the following information was inspired by [opensource.guide](https://opensource.guide/how-to-contribute/), a great site to learn all about open source software. 22 | 23 | If you've fixed a bug, started working on an enhancement, or done something else, you can [create a pull request](https://github.com/theiskaa/highlightable-text/pulls) to start a conversation about your changes. 24 | 25 | A pull request doesn't necessarily have to include finished work. It may be better to open a PR early on so that you can get feedback on your contribution. Just mention that it's a work in progress and keep adding commits. 26 | 27 | Here's how to contribute and submit your pull request: 28 | 29 | 1. [**Fork the repository**](https://help.github.com/articles/fork-a-repo/) and clone it locally. Add the original repository as a remote and pull in changes every so often so that you stay up to date with the project. 30 | 2. [**Create a branch**](https://guides.github.com/introduction/flow/) from `develop` for your changes. 31 | 3. **Add, commit, and push** your changes to your branch. 32 | 4. **Test out your changes.** Make sure that they work as you intended. 33 | 5. [**Open a pull request**](https://github.com/theiskaa/highlightable-text/pulls) to merge your branch into `develop`. 34 | - Reference any issues related to your PR (e.g. "Resolves #17"). 35 | - Describe your changes in detail and include screenshots if necessary. 36 | 6. :sunglasses: Sit back, relax, and wait for your PR to be reviewed. You might have to tweak your contribution or elaborate on your changes. That's OK, don't be afraid to justify your reasoning and ask questions. 37 | 38 | Thank you for reading through this contributing guide and welcome to the community! :tada: 39 | 40 | > We gonna make something easy to do. :v: 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 - (22/03/22) 2 | 3 | ### Updates: 4 | 5 | Resolved [#12](https://github.com/theiskaa/highlightable/issues/12) 6 | - Updated state behaviour type of [HighlightText] widget. 7 | - Improved default-text-determining by theme. 8 | 9 | ## 1.0.4 - (20/03/22) 10 | 11 | ### Updates: 12 | 13 | Resolved [#5](https://github.com/theiskaa/highlightable/issues/5) 14 | - Re-structured the whole widget to improve rendering speed. 15 | - Added a new model object to pass Regular-Expression patterns, as higlighting search options. 16 | 17 | **The new widget structure:** 18 | ``` 19 | ╭──────╮ Highlight 20 | │ Data │ ╭─────────────────────────────────╮ 21 | ╰──────╯ │ ╭─────────╮ ╭───────────────╮ │ 22 | │ ╭──▶│ │ Pattern │ & │ Words/Letters │ │ 23 | │ │ │ ╰─────────╯ ╰───────────────╯ │ 24 | │ │ ╰─────────────────────────────────╯ 25 | ╰───────╯ 26 | │ 27 | ╭── ▼ ──╮ ╭─────────────────────────────────╮ 28 | │ Parser ───▶ ... │ Highlighted Data as Text Widget │ 29 | ╰───────╯ ╰─────────────────────────────────╯ 30 | ``` 31 | 32 | ## 1.0.3 - (18/10/21) 33 | 34 | - Resolved [#6](https://github.com/theiskaa/highlightable/issues/6) (Added case sensitive support) 35 | 36 | #### Example: 37 | 38 | ```dart 39 | HighlightText( 40 | "Hello, Flutter!", 41 | highlightableWord: "flu, He", 42 | caseSensitive: true // Turn on case-sensitive. (as default it's false "disabled"). 43 | ), 44 | ``` 45 | 46 | ## 1.0.2 - (15/08/21) 47 | 48 | - Added `detectWord` property to focus on concrete matcher words 49 | 50 | #### Example: 51 | 52 | ```dart 53 | HighlightText( 54 | "Hello, Flutter!", 55 | highlightableWord: "flu, He", 56 | detectWords: true, 57 | defaultStyle: ... 58 | highlightStyle: ... 59 | ), 60 | ``` 61 | 62 | stwo 63 | 64 | --- 65 | 66 | ## 1.0.1 - (11/08/21) 67 | 68 | - Fixed lower/upper case matching problem 69 | 70 | ## 1.0.0 - (11/08/21) 71 |

72 | Package Logo 73 |

74 | 75 | --- 76 | 77 | ```dart 78 | HighlightText( 79 | 'Hello World', 80 | highlightableWord: 'hello', 81 | ), 82 | ``` 83 | 84 | s1 85 | 86 | --- 87 | 88 | 89 | License: MIT 90 | 91 | 92 | License: MIT 93 | 94 | 95 | CONTRIBUTING 96 | 97 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.12.10" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.7.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.8.0" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.8.1" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.1.0" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.2.0" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.4.2" 131 | typed_data: 132 | dependency: transitive 133 | description: 134 | name: typed_data 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.3.0" 138 | vector_math: 139 | dependency: transitive 140 | description: 141 | name: vector_math 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.1.0" 145 | sdks: 146 | dart: ">=2.12.0 <3.0.0" 147 | flutter: ">=1.17.0" 148 | -------------------------------------------------------------------------------- /lib/highlightable.dart: -------------------------------------------------------------------------------- 1 | library highlight; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:highlightable/highlight_model.dart'; 5 | 6 | export 'package:highlightable/highlight_model.dart'; 7 | 8 | // Highlight mode of widget, used as private value. 9 | // It isn't available for public API. 10 | enum _Mode { on, off } 11 | 12 | /// [HighlightText] is a [Text] widget alternative, that makes it easy to highlight 13 | /// concrete words, defined from [pattern] or from [pure string]. 14 | /// 15 | /// ╭──────╮ Highlight 16 | /// │ Data │ ╭─────────────────────────────────╮ 17 | /// ╰──────╯ │ ╭─────────╮ ╭───────────────╮ │ 18 | /// │ ╭───│ │ Pattern │ & │ Words/Letters │ │ 19 | /// │ │ │ ╰─────────╯ ╰───────────────╯ │ 20 | /// │ │ ╰─────────────────────────────────╯ 21 | /// ╰───────╯ 22 | /// │ 23 | /// ╭── ▼ ──╮ ╭─────────────────────────────────╮ 24 | /// │ Parser ───▶ ... │ Highlighted Data as Text Widget │ 25 | /// ╰───────╯ ╰─────────────────────────────────╯ 26 | /// 27 | class HighlightText extends StatelessWidget { 28 | /// The default string data. 29 | /// Like [Text] widget's first required value. 30 | /// 31 | /// For Example: When you use [Text] widget then you must to provide a string there. 32 | /// So, [HighlightText]'s [data] and normal [Text]'s [data] is absolutely same. 33 | /// 34 | /// Note: highlighting algorithm would search values inside that [data]. 35 | final String data; 36 | 37 | /// The highlight searching model. Has two options: 38 | /// │─▶ [Pattern] - A regex pattern that would work for each char of [data]. 39 | /// ╰─▶ [Words] - A list of highlightable words/letters. 40 | /// 41 | /// Example 42 | /// ```dart 43 | /// HighlightText( 44 | /// highlight: Highlight( 45 | /// pattern: r'\d', // Highlight each number in [data]. 46 | /// words: ['number', ...], // Highlight defined words in [data]. 47 | /// ), 48 | /// ... 49 | /// ) 50 | /// ``` 51 | final Highlight highlight; 52 | 53 | /// The text style for the text which wouldn't be highlighted. 54 | /// 55 | /// It's the style of [data] string, 56 | /// Would be appended to [non-highlight-matcher] values, from [data]. 57 | final TextStyle? style; 58 | 59 | /// The text style for the words/letters which would be highlighted. 60 | /// 61 | /// It's the style of [highlight] string, 62 | /// Would be appended to [highlight-matcher] values, from [data]. 63 | final TextStyle highlightStyle; 64 | 65 | /// Enables case sensitive of parsing algorithm. 66 | /// 67 | /// If unset, defaults to the ─▶ [false]. 68 | final bool caseSensitive; 69 | 70 | /// Enables only-word detection. 71 | /// It wouldn't highlight [matcher], if it's just a char. 72 | /// ╰─> {length == 1}. 73 | /// 74 | /// If unset, defaults to the ─▶ [false]. 75 | final bool detectWords; 76 | 77 | HighlightText( 78 | this.data, { 79 | Key? key, 80 | required this.highlight, 81 | this.style, 82 | this.highlightStyle = const TextStyle( 83 | color: Colors.blue, 84 | fontWeight: FontWeight.w600, 85 | ), 86 | this.caseSensitive = false, 87 | this.detectWords = false, 88 | }) : super(key: key); 89 | 90 | // The highlight sequence spans. 91 | // Which's actual [rich-text-widget]'s childrens value. 92 | final List _spans = []; 93 | 94 | // Main method that used to generate text splans. 95 | // Compares [data] to [highlight] to catch highlightable and non-highlightable 96 | // words/letters, which would be the "NEXT" text-span's text data. 97 | void _parse({ 98 | required TextStyle ds, 99 | required TextStyle hs, 100 | }) { 101 | if (_spans.isNotEmpty) _spans.clear(); 102 | 103 | if (highlight.words == null && highlight.pattern == null) { 104 | return; 105 | } 106 | 107 | // Matching sequence representer. 108 | String hp = ''; 109 | 110 | // Creates a span with [highlight-part] and [highlight-mode]. 111 | void cutHP(_Mode mode) { 112 | _createSpan(hp, mode, ds: ds, hs: hs); 113 | hp = ''; 114 | } 115 | 116 | for (var i = 0; i < data.length; i++) { 117 | if (hp.isEmpty) { 118 | hp += data[i]; 119 | continue; 120 | } 121 | 122 | // Cut [highlight-part] as [highlight-mode-off]. 123 | if (_isMatches(data[i]) && !_isMatches(hp)) { 124 | cutHP(_Mode.off); 125 | // Cut [highlight-part] as [highlight-mode-on]. 126 | } else if (!_isMatches(data[i]) && _isMatches(hp)) { 127 | final isNotDetectable = detectWords && hp.length == 1; 128 | cutHP(isNotDetectable ? _Mode.off : _Mode.on); 129 | } 130 | 131 | hp += data[i]; 132 | 133 | // Finish cutting highlight-part. 134 | if (hp.isNotEmpty && i == data.length - 1) { 135 | // Determine mode by matching of [element] and [highlight-part]+[element]. 136 | final matches = _isMatches(data[i]) && _isMatches(hp + data[i]); 137 | cutHP(matches ? _Mode.on : _Mode.off); 138 | } 139 | } 140 | } 141 | 142 | // [InlineSpan] generator. 143 | // Generates a text span appropriate by given data and mode. 144 | // 145 | // Mode determines the style of generated text-span. 146 | // If mode is on, text-span's style would be [highlightStyle]. 147 | // If not, style would be (default)[style]. 148 | void _createSpan( 149 | String data, 150 | _Mode mode, { 151 | required TextStyle ds, 152 | required TextStyle hs, 153 | }) { 154 | final textSpan = TextSpan( 155 | text: data, 156 | style: (mode == _Mode.on) ? hs : ds, 157 | ); 158 | 159 | _spans.add(textSpan); 160 | } 161 | 162 | // Matching checker for [parse] method. 163 | // Would be used to check matching of one letter or split word. 164 | bool _isMatches(String data) { 165 | // Ignore case sensitive. 166 | if (!caseSensitive) data = data.toLowerCase(); 167 | 168 | // Check matching by pattern. 169 | if (highlight.pattern != null && 170 | RegExp(highlight.pattern!).hasMatch(data)) { 171 | return true; 172 | } 173 | 174 | // Check matching by words/words. 175 | if (highlight.words != null && highlight.words!.isNotEmpty) { 176 | for (var i = 0; i < highlight.words!.length; i++) { 177 | var word = highlight.words![i]; 178 | 179 | // Ignore case sensitive. 180 | if (!caseSensitive) word = word.toLowerCase(); 181 | if (word.contains(data) || data.contains(word)) return true; 182 | } 183 | } 184 | 185 | return false; 186 | } 187 | 188 | @override 189 | Widget build(BuildContext context) { 190 | // Define style of normal text. 191 | final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); 192 | TextStyle effectiveStyle = style ?? defaultTextStyle.style; 193 | if (style == null || style!.inherit) { 194 | effectiveStyle = defaultTextStyle.style.merge(style); 195 | } 196 | 197 | // Generate highlight-sequence text-spans. 198 | _parse(ds: effectiveStyle, hs: highlightStyle); 199 | 200 | // Build the default text, if there is no spans. 201 | if (_spans.isEmpty) return Text(data, style: effectiveStyle); 202 | 203 | // Build the highlighted text with rich-text. 204 | return RichText(text: TextSpan(text: '', children: _spans)); 205 | } 206 | } 207 | --------------------------------------------------------------------------------