├── .github ├── FUNDING.yml └── workflows │ ├── publish.yml │ ├── publishable.yml │ └── runnable.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── 40.png │ ├── love.png │ └── sun_glasses.png ├── ff_annotation_route_commands ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── example_route.dart │ ├── example_routes.dart │ ├── generated_plugin_registrant.dart │ ├── main.dart │ ├── pages │ │ ├── background_text_demo.dart │ │ ├── custom_text_overflow_demo.dart │ │ ├── gradient_text.dart │ │ ├── join_zero_width_space.dart │ │ ├── main_page.dart │ │ ├── regexp_text_demo.dart │ │ ├── search_highlight_demo.dart │ │ ├── selectable_region_width_text_field_demo.dart │ │ ├── selection_area_demo.dart │ │ ├── text_demo.dart │ │ └── text_selection_demo.dart │ └── text │ │ ├── highlight_text_span_builder.dart │ │ ├── my_extended_text_selection_controls.dart │ │ ├── my_special_text_span_builder.dart │ │ ├── regexp_special_text_span_builder.dart │ │ └── selection_area.dart ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── extended_text.iml ├── lib ├── extended_text.dart └── src │ ├── extended │ ├── gradient │ │ ├── gradient_config.dart │ │ └── gradient_mixin.dart │ ├── rendering │ │ └── paragraph.dart │ ├── selection_mixin.dart │ ├── text_overflow_mixin.dart │ └── widgets │ │ ├── rich_text.dart │ │ ├── text.dart │ │ └── text_overflow_widget.dart │ └── official │ ├── rendering │ └── paragraph.dart │ └── widgets │ ├── rich_text.dart │ └── text.dart ├── pubspec.yaml └── test └── main_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: zmtzawqlp 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | custom: http://zmtzawqlp.gitee.io/my_images/images/qrcode.png 13 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v3 14 | - name: Publish 15 | uses: k-paxian/dart-package-publisher@master 16 | with: 17 | credentialJson: ${{ secrets.CREDENTIAL_JSON }} 18 | flutter: true 19 | skipTests: true -------------------------------------------------------------------------------- /.github/workflows/publishable.yml: -------------------------------------------------------------------------------- 1 | name: Publishable 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths: 11 | - "**.md" 12 | - "**.yaml" 13 | - "**.yml" 14 | 15 | jobs: 16 | publish-dry-run: 17 | name: Publish dry-run with packages 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: k-paxian/dart-package-publisher@master 22 | with: 23 | credentialJson: 'MockCredentialJson' 24 | flutter: true 25 | dryRunOnly: true 26 | skipTests: true -------------------------------------------------------------------------------- /.github/workflows/runnable.yml: -------------------------------------------------------------------------------- 1 | name: Runnable (stable) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths-ignore: 11 | - "**.md" 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze on ${{ matrix.os }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [ ubuntu-latest ] 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: actions/setup-java@v3 23 | with: 24 | distribution: 'adopt' 25 | java-version: '11.x' 26 | - uses: subosito/flutter-action@v2 27 | with: 28 | channel: 'stable' 29 | - name: Log Dart/Flutter versions 30 | run: | 31 | dart --version 32 | flutter --version 33 | - name: Prepare dependencies 34 | run: flutter pub get 35 | - name: Analyse the repo 36 | run: flutter analyze lib example/lib 37 | - name: Run tests 38 | run: flutter test 39 | - name: Generate docs 40 | run: | 41 | dart pub global activate dartdoc 42 | dart pub global run dartdoc . 43 | 44 | test_iOS: 45 | needs: analyze 46 | name: Test iOS 47 | runs-on: macos-latest 48 | steps: 49 | - uses: actions/checkout@v3 50 | - uses: actions/setup-java@v3 51 | with: 52 | distribution: 'adopt' 53 | java-version: '11.x' 54 | - uses: subosito/flutter-action@v2.8.0 55 | with: 56 | channel: stable 57 | - run: dart --version 58 | - run: flutter --version 59 | - run: flutter pub get 60 | - run: cd example; flutter build ios --no-codesign 61 | 62 | test_android: 63 | needs: analyze 64 | name: Test Android 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v3 68 | - uses: actions/setup-java@v3 69 | with: 70 | distribution: 'adopt' 71 | java-version: '11.x' 72 | - uses: subosito/flutter-action@v2.8.0 73 | with: 74 | channel: stable 75 | - run: dart --version 76 | - run: flutter --version 77 | - run: flutter pub get 78 | - run: sudo echo "y" | sudo $ANDROID_HOME/tools/bin/sdkmanager "ndk;21.4.7075529" 79 | - run: cd example; flutter build apk --debug -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | 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: 63b2daff7f91afeaac47f3646f59eefd59210c41 8 | channel: unknown 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "extended_text", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "example", 14 | "cwd": "example", 15 | "request": "launch", 16 | "type": "dart" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 15.0.2 2 | 3 | * Fix long Text Cropping Issue with Ellipsis Middle Overflow and setState (#175) 4 | 5 | ## 15.0.1 6 | 7 | * Fix issue that setState() causes custom overflow to disappear(#171) 8 | * Fix issue that can' find no overflow(#167,#174) 9 | * Fix issue that OverflowWidget not work well when the size of window is changing. 10 | 11 | ## 15.0.0 12 | 13 | * Migrate to Flutter 3.29.0 14 | 15 | ## 14.2.0 16 | 17 | * Add TextOverflowPosition.auto and SearchHighlightDemo 18 | * Improve the algorithm efficiency of [findNoOverflow] 19 | 20 | ## 14.1.0 21 | 22 | * Support gradient text 23 | 24 | ## 14.0.1 25 | 26 | * Implement SelectionArea triple click gestures ([#144563](https://github.com/flutter/flutter/pull/144563)) 27 | 28 | ## 14.0.0 29 | 30 | * Migrate to Flutter 3.24.0 31 | 32 | ## 13.0.2 33 | 34 | * Adapt the `getFullHeightForCaret` method on 3.19.0 and 3.22.0 35 | 36 | ## 13.0.1 37 | 38 | * Merge https://github.com/flutter/flutter/pull/144577 39 | 40 | ## 13.0.0 41 | 42 | * Migrate to Flutter 3.19.0 43 | 44 | ## 12.0.1 45 | 46 | * Update readme about HarmonyOS 47 | 48 | ## 12.0.0 49 | 50 | * Migrate to Flutter 3.16.0 51 | 52 | 53 | ## 11.1.0 54 | 55 | * Migrate to Flutter 3.13.0 56 | 57 | ## 11.0.1 58 | 59 | * Fix Flutter 3.10 onTap not triggered inside TextOverflowWidget #147 60 | 61 | ## 11.0.0 62 | 63 | * Migrate to Flutter 3.10.0 64 | * Refactoring codes and sync codes from 3.10.0 65 | * Support SelectionArea 66 | * Breaking change: 67 | Remove [ExtendedText.textSelectionGestureDetectorBuilder],[ExtendedText.shouldShowSelectionHandles],[ExtendedText.selectionHeightStyle],[sExtendedText.electionWidthStyle],[ExtendedText.dragStartBehavior],[ExtendedText.selectionEnabled], [ExtendedTextSelectionPointerHandler]. They are all refer to selection function. It's replaced by SelectionArea. 68 | * Add [ExtendedText.canSelectPlaceholderSpan] control selection behavior. 69 | 70 | ## 10.0.1 71 | 72 | * fix issue on ios after flutter version 3.7.0. #191 #198 73 | 74 | ## 10.0.0 75 | 76 | * Migrate to 3.7.0 77 | 78 | ## 9.1.2 79 | 80 | * Add TextOverflowWidget.clearType 81 | 82 | ## 9.1.1 83 | 84 | * Fix cutOffInlineSpan should take care of emoji #131. 85 | 86 | ## 9.1.0 87 | 88 | * Migrate to 3.0.0 89 | 90 | ## 9.0.0 91 | 92 | * Migrate to 2.10.0. 93 | * Add shouldShowSelectionHandles and textSelectionGestureDetectorBuilder call back to define the behavior of handles and toolbar. 94 | * Shortcut support for web and desktop. 95 | 96 | ## 8.0.2 97 | 98 | * Fix selectionWidthStyle and selectionHeightStyle are not working. 99 | 100 | ## 8.0.1 101 | 102 | * Support copy on desktop 103 | 104 | ## 8.0.0 105 | 106 | * Migrate to 2.8 107 | 108 | ## 7.0.1 109 | 110 | * Stop hittest if overflowWidget is not hit but overflowRect contains hit pointer. 111 | 112 | ## 7.0.0 113 | 114 | * Add [SpecialTextSpan.mouseCursor], [SpecialTextSpan.onEnter] and [SpecialTextSpan.onExit]. 115 | * merge code from 2.2.0 116 | 117 | ## 6.0.6 118 | 119 | * Fix overflow rect is not right if overflowSelection has no selection(may be empty text). 120 | 121 | ## 6.0.5 122 | 123 | * Remove unnecessary assert (assert(textPainter.width >= lastChild!.size.width)) 124 | * Initialize _offset with Offset.zero. 125 | 126 | ## 6.0.4 127 | 128 | * Fix find no overflow endless loop. #105 129 | * Store raw text to reduce layout. 130 | 131 | ## 6.0.3 132 | 133 | * Fix hitTest is failed when set TextOverflowWidget and selectionEnabled false. 134 | * Fix text is cut off when set TextOverflowPosition.end. 135 | 136 | ## 6.0.2 137 | 138 | * Remove unnecessary canvas.save() when clear _overflowRect 139 | 140 | ## 6.0.1 141 | 142 | * Improve performance when find no overflow. 143 | 144 | ## 6.0.0 145 | 146 | * Add [TextOverflowWidget.position] to support show overflow at start, middle or end. 147 | https://github.com/flutter/flutter/issues/45336 148 | * Add [ExtendedText.joinZeroWidthSpace] to make line breaking and overflow style better. 149 | https://github.com/flutter/flutter/issues/18761 150 | * Fix strutStyle not work. 151 | * Breaking change: remove [TextOverflowWidget.fixedOffset] 152 | * Breaking change: [SpecialText.getContent] is not include endflag now.(please check if you call getContent and your endflag length is more than 1) 153 | 154 | ## 5.0.5 155 | 156 | * Fix issue that childIndex == children.length assert false in assembleSemanticsNode when use overflowWidget and text is not overflow. 157 | 158 | ## 5.0.4 159 | 160 | * Fix issue that the overflowWidget is not layout #97 161 | 162 | ## 5.0.3 163 | 164 | * Fix null-safety error #96 165 | 166 | ## 5.0.2 167 | 168 | * Fix null-safety error 169 | 170 | ## 5.0.1 171 | 172 | * Add add SemanticsInformation for overflowWidget 173 | * Improve performance for overflowWidget 174 | * Do not paint Selection in the region of overFlowWidget 175 | 176 | ## 5.0.0 177 | 178 | * Support null-safety 179 | 180 | ## 4.1.0 181 | 182 | * Support keyboard copy on web/desktop 183 | * Fix wrong position of caret 184 | 185 | ## 4.0.1 186 | 187 | * Change handleSpecialText to hasSpecialInlineSpanBase(extended_text_library) 188 | * Add hasPlaceholderSpan(extended_text_library) 189 | * Fix wrong offset of WidgetSpan #86 190 | 191 | ## 4.0.0 192 | 193 | * Merge from Flutter v1.20 194 | 195 | ## 3.0.1 196 | 197 | * Fix throw exception when set OverflowWidget and has no visual overflow. 198 | 199 | ## 3.0.0 200 | 201 | * Breaking change: fix typos OverflowWidget. 202 | 203 | ## 2.0.0 204 | 205 | * Support OverflowWidget ExtendedText. 206 | * Breaking change: remove overflowTextSpan. 207 | 208 | ## 1.0.1 209 | 210 | * Fix wrong calculation about selection handles. 211 | 212 | ## 1.0.0 213 | 214 | * Merge code from 1.17.0 215 | * Fix analysis_options 216 | 217 | ## 0.7.1 218 | 219 | * Fix error about TargetPlatform.macOS 220 | 221 | ## 0.7.0 222 | 223 | * Fix issue that Index out of range for overflow WidgetSpan 224 | 225 | ## 0.6.9 226 | 227 | * Fix issue that TextPainter was not layout 228 | 229 | ## 0.6.8 230 | 231 | * extract method for TextSelection 232 | 233 | ## 0.6.7 234 | 235 | * codes base on 1.12.13+hotfix.5 236 | * set limitation of flutter sdk >=1.12.13 <1.12.16 237 | 238 | ## 0.6.6 239 | 240 | * Fix kMinInteractiveSize is missing in high version of flutter 241 | * Fix text overflow about WidgetSpan 242 | 243 | ## 0.6.4 244 | 245 | * Improve codes about selection 246 | * Select all SpecialTextSpan which deleteAll is true when double tap or long tap 247 | 248 | ## 0.6.3 249 | 250 | * Fix issue ImageSpan is not TextSpan(https://github.com/fluttercandies/extended_text/issues/24) 251 | 252 | ## 0.6.2 253 | 254 | * Fix wrong selection offset 255 | * Fix wrong text clip due to overflowspan 256 | 257 | ## 0.6.1 258 | 259 | * Fix issue type 'List' is not a subtype of type 'List'(https://github.com/fluttercandies/extended_text/issues/20) 260 | 261 | ## 0.6.0 262 | 263 | * Improve codes base on v1.7.8 264 | * Support WidgetSpan (ExtendedWidgetSpan) 265 | 266 | ## 0.5.8 267 | 268 | * Breaking change: 269 | Remove background parameter of OverFlowTextSpan 270 | 271 | ## 0.5.7 272 | 273 | * Issue: 274 | Fix textEditingValue and textSelectionControls are not update when didUpdateWidget 275 | 276 | ## 0.5.4 277 | 278 | * Feature: 279 | Support text selection 280 | * Issue: 281 | Fix issue about rect of overFlowTextSpan 282 | 283 | ## 0.5.3 284 | 285 | * Update extended_text_library 286 | 287 | ## 0.5.2 288 | 289 | * Update path_provider 1.1.0 290 | 291 | ## 0.5.0 292 | 293 | * Update extended_text_library 294 | Remove caretIn parameter(SpecialTextSpan) 295 | DeleteAll parameter has the same effect as caretIn parameter(SpecialTextSpan) 296 | 297 | ## 0.4.9 298 | 299 | * Fix wrong background rect of OverFlowTextSpan when over flow area has image span 300 | 301 | ## 0.4.8 302 | 303 | * Fix wrong background rect of OverFlowTextSpan(issue 6) 304 | 305 | ## 0.4.7 306 | 307 | * Disabled informationCollector to keep backwards compatibility for now (ExtendedNetworkImageProvider) 308 | 309 | ## 0.4.5 310 | 311 | * Add GestureRecognizer for ImageSpan 312 | * Add demo to show image in photo view 313 | 314 | ## 0.4.3 315 | 316 | * Handle image span load failed 317 | 318 | ## 0.4.2 319 | 320 | * Update extended_text_library for cache folder is changed 321 | 322 | ## 0.4.0 323 | 324 | * Update extended_text_library for BackgroundTextSpan 325 | 326 | ## 0.3.9 327 | 328 | * Override compareTo method in BackgroundTextSpan and OverFlowTextSpan to 329 | Fix issue that it was error rendering 330 | 331 | ## 0.3.8 332 | 333 | * Import extended_text_library 334 | 335 | ## 0.3.4 336 | 337 | * Fix issue that tap exception throw when use OverFlowTextSpan 338 | 339 | ## 0.3.1 340 | 341 | * Add clearFailedCache parameter for CachedNetworkImage 342 | Add clearLoadFailedImageMemoryCache method 343 | Both them are used to clear image load failed memory cache, so that image will be reloaded 344 | 345 | ## 0.2.9 346 | 347 | * Update path_provider version from 0.4.1 to 0.5.0+1 348 | 349 | ## 0.2.8 350 | 351 | * Change SpecialTextGestureTapCallback input from string to dynamic 352 | 353 | ## 0.2.7 354 | 355 | * Change BeforePaintImage function to BeforePaintTextImage 356 | Change AfterPaintImage function to AfterPaintTextImage 357 | 358 | ## 0.2.5 359 | 360 | * Fix issue that BackgroundTextSpan has error clip. 361 | 362 | ## 0.2.4 363 | 364 | * Add TextPainter wholeTextPainter for BackgroundTextSpan's paintBackground call back,so that you can get info for 365 | whole text painter. 366 | 367 | ## 0.2.2 368 | 369 | * Fix issue that find TextPosition near overflow is not accurate. 370 | 371 | ## 0.2.1 372 | 373 | * Use ExtendedTextOverflow to replace TextOverflow(new flutter sdk TextOverflow has new value TextOverflow.visible) 374 | 375 | ## 0.1.8 376 | 377 | * Suport inline image, custom background ,custom over flow. 378 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zmtzawqlp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zmtzawqlp 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. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Specify analysis options. 2 | # 3 | # Until there are meta linter rules, each desired lint must be explicitly enabled. 4 | # See: https://github.com/dart-lang/linter/issues/288 5 | # 6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/ 7 | # See the configuration guide for more 8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer 9 | # 10 | # There are other similar analysis options files in the flutter repos, 11 | # which should be kept in sync with this file: 12 | # 13 | # - analysis_options.yaml (this file) 14 | # - packages/flutter/lib/analysis_options_user.yaml 15 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml 16 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml 17 | # 18 | # This file contains the analysis options used by Flutter tools, such as IntelliJ, 19 | # Android Studio, and the `flutter analyze` command. 20 | 21 | analyzer: 22 | errors: 23 | # treat missing required parameters as a warning (not a hint) 24 | missing_required_param: warning 25 | # treat missing returns as a warning (not a hint) 26 | missing_return: warning 27 | # allow having TODOs in the code 28 | todo: ignore 29 | # Ignore analyzer hints for updating pubspecs when using Future or 30 | # Stream and not importing dart:async 31 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 32 | # sdk_version_async_exported_from_core: ignore 33 | # exclude: 34 | # - "bin/cache/**" 35 | # # the following two are relative to the stocks example and the flutter package respectively 36 | # # see https://github.com/dart-lang/sdk/issues/28463 37 | # - "lib/i18n/messages_*.dart" 38 | # - "lib/src/http/**" 39 | 40 | linter: 41 | rules: 42 | # these rules are documented on and in the same order as 43 | # the Dart Lint rules page to make maintenance easier 44 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 45 | - always_declare_return_types 46 | - always_put_control_body_on_new_line 47 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 48 | - always_require_non_null_named_parameters 49 | - always_specify_types 50 | - annotate_overrides 51 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 52 | # - avoid_as # required for implicit-casts: true 53 | - avoid_bool_literals_in_conditional_expressions 54 | # - avoid_catches_without_on_clauses # we do this commonly 55 | # - avoid_catching_errors # we do this commonly 56 | - avoid_classes_with_only_static_members 57 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 58 | - avoid_empty_else 59 | # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested 60 | - avoid_field_initializers_in_const_classes 61 | - avoid_function_literals_in_foreach_calls 62 | # - avoid_implementing_value_types # not yet tested 63 | - avoid_init_to_null 64 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 65 | - avoid_null_checks_in_equality_operators 66 | # - avoid_positional_boolean_parameters # not yet tested 67 | # - avoid_print # not yet tested 68 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 69 | # - avoid_redundant_argument_values # not yet tested 70 | - avoid_relative_lib_imports 71 | - avoid_renaming_method_parameters 72 | - avoid_return_types_on_setters 73 | # - avoid_returning_null # there are plenty of valid reasons to return null 74 | # - avoid_returning_null_for_future # not yet tested 75 | - avoid_returning_null_for_void 76 | # - avoid_returning_this # there are plenty of valid reasons to return this 77 | # - avoid_setters_without_getters # not yet tested 78 | # - avoid_shadowing_type_parameters # not yet tested 79 | - avoid_single_cascade_in_expression_statements 80 | - avoid_slow_async_io 81 | - avoid_types_as_parameter_names 82 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 83 | # - avoid_unnecessary_containers # not yet tested 84 | - avoid_unused_constructor_parameters 85 | - avoid_void_async 86 | # - avoid_web_libraries_in_flutter # not yet tested 87 | - await_only_futures 88 | - camel_case_extensions 89 | - camel_case_types 90 | - cancel_subscriptions 91 | # - cascade_invocations # not yet tested 92 | # - close_sinks # not reliable enough 93 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 94 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 95 | - control_flow_in_finally 96 | # - curly_braces_in_flow_control_structures # not yet tested 97 | # - diagnostic_describe_all_properties # not yet tested 98 | - directives_ordering 99 | - empty_catches 100 | - empty_constructor_bodies 101 | - empty_statements 102 | # - file_names # not yet tested 103 | - flutter_style_todos 104 | - hash_and_equals 105 | - implementation_imports 106 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 107 | # - iterable_contains_unrelated_type 108 | # - join_return_with_assignment # not yet tested 109 | - library_names 110 | - library_prefixes 111 | # - lines_longer_than_80_chars # not yet tested 112 | # - list_remove_unrelated_type 113 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 114 | # - missing_whitespace_between_adjacent_strings # not yet tested 115 | - no_adjacent_strings_in_list 116 | - no_duplicate_case_values 117 | # - no_logic_in_create_state # not yet tested 118 | # - no_runtimeType_toString # not yet tested 119 | - non_constant_identifier_names 120 | # - null_closures # not yet tested 121 | # - omit_local_variable_types # opposite of always_specify_types 122 | # - one_member_abstracts # too many false positives 123 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 124 | - overridden_fields 125 | - package_api_docs 126 | - package_names 127 | - package_prefixed_library_names 128 | # - parameter_assignments # we do this commonly 129 | - prefer_adjacent_string_concatenation 130 | - prefer_asserts_in_initializer_lists 131 | # - prefer_asserts_with_message # not yet tested 132 | - prefer_collection_literals 133 | - prefer_conditional_assignment 134 | - prefer_const_constructors 135 | - prefer_const_constructors_in_immutables 136 | - prefer_const_declarations 137 | - prefer_const_literals_to_create_immutables 138 | # - prefer_constructors_over_static_methods # not yet tested 139 | - prefer_contains 140 | # - prefer_double_quotes # opposite of prefer_single_quotes 141 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 142 | - prefer_final_fields 143 | - prefer_final_in_for_each 144 | - prefer_final_locals 145 | - prefer_for_elements_to_map_fromIterable 146 | - prefer_foreach 147 | # - prefer_function_declarations_over_variables # not yet tested 148 | - prefer_generic_function_type_aliases 149 | - prefer_if_elements_to_conditional_expressions 150 | - prefer_if_null_operators 151 | - prefer_initializing_formals 152 | - prefer_inlined_adds 153 | # - prefer_int_literals # not yet tested 154 | # - prefer_interpolation_to_compose_strings # not yet tested 155 | - prefer_is_empty 156 | - prefer_is_not_empty 157 | - prefer_is_not_operator 158 | - prefer_iterable_whereType 159 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 160 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 161 | # - prefer_relative_imports # not yet tested 162 | - prefer_single_quotes 163 | - prefer_spread_collections 164 | - prefer_typing_uninitialized_variables 165 | - prefer_void_to_null 166 | # - provide_deprecation_message # not yet tested 167 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 168 | - recursive_getters 169 | - slash_for_doc_comments 170 | # - sort_child_properties_last # not yet tested 171 | - sort_constructors_first 172 | - sort_pub_dependencies 173 | - sort_unnamed_constructors_first 174 | - test_types_in_equals 175 | - throw_in_finally 176 | # - type_annotate_public_apis # subset of always_specify_types 177 | - type_init_formals 178 | # - unawaited_futures # too many false positives 179 | # - unnecessary_await_in_return # not yet tested 180 | - unnecessary_brace_in_string_interps 181 | - unnecessary_const 182 | # - unnecessary_final # conflicts with prefer_final_locals 183 | - unnecessary_getters_setters 184 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 185 | - unnecessary_new 186 | - unnecessary_null_aware_assignments 187 | - unnecessary_null_in_if_null_operators 188 | - unnecessary_overrides 189 | - unnecessary_parenthesis 190 | - unnecessary_statements 191 | - unnecessary_string_interpolations 192 | - unnecessary_this 193 | - unrelated_type_equality_checks 194 | # - unsafe_html # not yet tested 195 | - use_full_hex_values_for_flutter_colors 196 | # - use_function_type_syntax_for_parameters # not yet tested 197 | # - use_key_in_widget_constructors # not yet tested 198 | - use_rethrow_when_possible 199 | # - use_setters_to_change_properties # not yet tested 200 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 201 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 202 | - valid_regexps 203 | - void_checks 204 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | .flutter-plugins-dependencies 73 | assets.preview.dart -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 17 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 18 | - platform: android 19 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 20 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 21 | - platform: ios 22 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 23 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 24 | - platform: linux 25 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 26 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 27 | - platform: macos 28 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 29 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 30 | - platform: web 31 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 32 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 33 | - platform: windows 34 | create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 35 | base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter application. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Specify analysis options. 2 | # 3 | # Until there are meta linter rules, each desired lint must be explicitly enabled. 4 | # See: https://github.com/dart-lang/linter/issues/288 5 | # 6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/ 7 | # See the configuration guide for more 8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer 9 | # 10 | # There are other similar analysis options files in the flutter repos, 11 | # which should be kept in sync with this file: 12 | # 13 | # - analysis_options.yaml (this file) 14 | # - packages/flutter/lib/analysis_options_user.yaml 15 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml 16 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml 17 | # 18 | # This file contains the analysis options used by Flutter tools, such as IntelliJ, 19 | # Android Studio, and the `flutter analyze` command. 20 | 21 | analyzer: 22 | errors: 23 | # treat missing required parameters as a warning (not a hint) 24 | missing_required_param: warning 25 | # treat missing returns as a warning (not a hint) 26 | missing_return: warning 27 | # allow having TODOs in the code 28 | todo: ignore 29 | # Ignore analyzer hints for updating pubspecs when using Future or 30 | # Stream and not importing dart:async 31 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 32 | # sdk_version_async_exported_from_core: ignore 33 | # exclude: 34 | # - "bin/cache/**" 35 | # # the following two are relative to the stocks example and the flutter package respectively 36 | # # see https://github.com/dart-lang/sdk/issues/28463 37 | # - "lib/i18n/messages_*.dart" 38 | # - "lib/src/http/**" 39 | 40 | linter: 41 | rules: 42 | # these rules are documented on and in the same order as 43 | # the Dart Lint rules page to make maintenance easier 44 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 45 | - always_declare_return_types 46 | - always_put_control_body_on_new_line 47 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 48 | - always_require_non_null_named_parameters 49 | - always_specify_types 50 | - annotate_overrides 51 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 52 | # - avoid_as # required for implicit-casts: true 53 | - avoid_bool_literals_in_conditional_expressions 54 | # - avoid_catches_without_on_clauses # we do this commonly 55 | # - avoid_catching_errors # we do this commonly 56 | - avoid_classes_with_only_static_members 57 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 58 | - avoid_empty_else 59 | # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested 60 | - avoid_field_initializers_in_const_classes 61 | - avoid_function_literals_in_foreach_calls 62 | # - avoid_implementing_value_types # not yet tested 63 | - avoid_init_to_null 64 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 65 | - avoid_null_checks_in_equality_operators 66 | # - avoid_positional_boolean_parameters # not yet tested 67 | # - avoid_print # not yet tested 68 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 69 | # - avoid_redundant_argument_values # not yet tested 70 | - avoid_relative_lib_imports 71 | - avoid_renaming_method_parameters 72 | - avoid_return_types_on_setters 73 | # - avoid_returning_null # there are plenty of valid reasons to return null 74 | # - avoid_returning_null_for_future # not yet tested 75 | - avoid_returning_null_for_void 76 | # - avoid_returning_this # there are plenty of valid reasons to return this 77 | # - avoid_setters_without_getters # not yet tested 78 | # - avoid_shadowing_type_parameters # not yet tested 79 | - avoid_single_cascade_in_expression_statements 80 | - avoid_slow_async_io 81 | - avoid_types_as_parameter_names 82 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 83 | # - avoid_unnecessary_containers # not yet tested 84 | - avoid_unused_constructor_parameters 85 | - avoid_void_async 86 | # - avoid_web_libraries_in_flutter # not yet tested 87 | - await_only_futures 88 | - camel_case_extensions 89 | - camel_case_types 90 | - cancel_subscriptions 91 | # - cascade_invocations # not yet tested 92 | # - close_sinks # not reliable enough 93 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 94 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 95 | - control_flow_in_finally 96 | # - curly_braces_in_flow_control_structures # not yet tested 97 | # - diagnostic_describe_all_properties # not yet tested 98 | - directives_ordering 99 | - empty_catches 100 | - empty_constructor_bodies 101 | - empty_statements 102 | # - file_names # not yet tested 103 | - flutter_style_todos 104 | - hash_and_equals 105 | - implementation_imports 106 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 107 | # - iterable_contains_unrelated_type 108 | # - join_return_with_assignment # not yet tested 109 | - library_names 110 | - library_prefixes 111 | # - lines_longer_than_80_chars # not yet tested 112 | # - list_remove_unrelated_type 113 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 114 | # - missing_whitespace_between_adjacent_strings # not yet tested 115 | - no_adjacent_strings_in_list 116 | - no_duplicate_case_values 117 | # - no_logic_in_create_state # not yet tested 118 | # - no_runtimeType_toString # not yet tested 119 | - non_constant_identifier_names 120 | # - null_closures # not yet tested 121 | # - omit_local_variable_types # opposite of always_specify_types 122 | # - one_member_abstracts # too many false positives 123 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 124 | - overridden_fields 125 | - package_api_docs 126 | - package_names 127 | - package_prefixed_library_names 128 | # - parameter_assignments # we do this commonly 129 | - prefer_adjacent_string_concatenation 130 | - prefer_asserts_in_initializer_lists 131 | # - prefer_asserts_with_message # not yet tested 132 | - prefer_collection_literals 133 | - prefer_conditional_assignment 134 | - prefer_const_constructors 135 | - prefer_const_constructors_in_immutables 136 | - prefer_const_declarations 137 | - prefer_const_literals_to_create_immutables 138 | # - prefer_constructors_over_static_methods # not yet tested 139 | - prefer_contains 140 | # - prefer_double_quotes # opposite of prefer_single_quotes 141 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 142 | - prefer_final_fields 143 | - prefer_final_in_for_each 144 | - prefer_final_locals 145 | - prefer_for_elements_to_map_fromIterable 146 | - prefer_foreach 147 | # - prefer_function_declarations_over_variables # not yet tested 148 | - prefer_generic_function_type_aliases 149 | - prefer_if_elements_to_conditional_expressions 150 | - prefer_if_null_operators 151 | - prefer_initializing_formals 152 | - prefer_inlined_adds 153 | # - prefer_int_literals # not yet tested 154 | # - prefer_interpolation_to_compose_strings # not yet tested 155 | - prefer_is_empty 156 | - prefer_is_not_empty 157 | - prefer_is_not_operator 158 | - prefer_iterable_whereType 159 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 160 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 161 | # - prefer_relative_imports # not yet tested 162 | - prefer_single_quotes 163 | - prefer_spread_collections 164 | - prefer_typing_uninitialized_variables 165 | - prefer_void_to_null 166 | # - provide_deprecation_message # not yet tested 167 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 168 | - recursive_getters 169 | - slash_for_doc_comments 170 | # - sort_child_properties_last # not yet tested 171 | - sort_constructors_first 172 | - sort_pub_dependencies 173 | - sort_unnamed_constructors_first 174 | - test_types_in_equals 175 | - throw_in_finally 176 | # - type_annotate_public_apis # subset of always_specify_types 177 | - type_init_formals 178 | # - unawaited_futures # too many false positives 179 | # - unnecessary_await_in_return # not yet tested 180 | - unnecessary_brace_in_string_interps 181 | - unnecessary_const 182 | # - unnecessary_final # conflicts with prefer_final_locals 183 | - unnecessary_getters_setters 184 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 185 | - unnecessary_new 186 | - unnecessary_null_aware_assignments 187 | - unnecessary_null_in_if_null_operators 188 | - unnecessary_overrides 189 | - unnecessary_parenthesis 190 | - unnecessary_statements 191 | - unnecessary_string_interpolations 192 | - unnecessary_this 193 | - unrelated_type_equality_checks 194 | # - unsafe_html # not yet tested 195 | - use_full_hex_values_for_flutter_colors 196 | # - use_function_type_syntax_for_parameters # not yet tested 197 | # - use_key_in_widget_constructors # not yet tested 198 | - use_rethrow_when_possible 199 | # - use_setters_to_change_properties # not yet tested 200 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 201 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 202 | - valid_regexps 203 | - void_checks 204 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | namespace "com.example.example" 30 | compileSdkVersion flutter.compileSdkVersion 31 | ndkVersion flutter.ndkVersion 32 | 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | 38 | kotlinOptions { 39 | jvmTarget = '1.8' 40 | } 41 | 42 | sourceSets { 43 | main.java.srcDirs += 'src/main/kotlin' 44 | } 45 | 46 | defaultConfig { 47 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 48 | applicationId "com.example.example" 49 | // You can update the following values to match your application needs. 50 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 51 | minSdkVersion flutter.minSdkVersion 52 | targetSdkVersion flutter.targetSdkVersion 53 | versionCode flutterVersionCode.toInteger() 54 | versionName flutterVersionName 55 | } 56 | 57 | buildTypes { 58 | release { 59 | // TODO: Add your own signing config for the release build. 60 | // Signing with the debug keys for now, so `flutter run --release` works. 61 | signingConfig signingConfigs.debug 62 | } 63 | } 64 | } 65 | 66 | flutter { 67 | source '../..' 68 | } 69 | 70 | dependencies { 71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 72 | } 73 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/assets/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/assets/40.png -------------------------------------------------------------------------------- /example/assets/love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/assets/love.png -------------------------------------------------------------------------------- /example/assets/sun_glasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/assets/sun_glasses.png -------------------------------------------------------------------------------- /example/ff_annotation_route_commands: -------------------------------------------------------------------------------- 1 | -s -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/example_route.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY MANUALLY 2 | // ************************************************************************** 3 | // Auto generated by https://github.com/fluttercandies/ff_annotation_route 4 | // ************************************************************************** 5 | // fast mode: true 6 | // version: 10.1.0 7 | // ************************************************************************** 8 | // ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import,unnecessary_import,unused_shown_name,implementation_imports,duplicate_import,library_private_types_in_public_api 9 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 10 | import 'package:flutter/widgets.dart'; 11 | 12 | import 'pages/background_text_demo.dart'; 13 | import 'pages/custom_text_overflow_demo.dart'; 14 | import 'pages/gradient_text.dart'; 15 | import 'pages/join_zero_width_space.dart'; 16 | import 'pages/main_page.dart'; 17 | import 'pages/regexp_text_demo.dart'; 18 | import 'pages/search_highlight_demo.dart'; 19 | import 'pages/selectable_region_width_text_field_demo.dart'; 20 | import 'pages/selection_area_demo.dart'; 21 | import 'pages/text_demo.dart'; 22 | 23 | /// Get route settings base on route name, auto generated by https://github.com/fluttercandies/ff_annotation_route 24 | FFRouteSettings getRouteSettings({ 25 | required String name, 26 | Map? arguments, 27 | PageBuilder? notFoundPageBuilder, 28 | }) { 29 | final Map safeArguments = 30 | arguments ?? const {}; 31 | switch (name) { 32 | case 'fluttercandies://BackgroundTextDemo': 33 | return FFRouteSettings( 34 | name: name, 35 | arguments: arguments, 36 | builder: () => BackgroundTextDemo(), 37 | routeName: 'BackgroundText', 38 | description: 'workaround for issue 24335/24337 about background', 39 | ); 40 | case 'fluttercandies://CustomTextOverflowDemo': 41 | return FFRouteSettings( 42 | name: name, 43 | arguments: arguments, 44 | builder: () => CustomTextOverflowDemo(), 45 | routeName: 'CustomTextOverflow', 46 | description: 'workaround for issue 26748. how to custom text overflow', 47 | ); 48 | case 'fluttercandies://GradientText': 49 | return FFRouteSettings( 50 | name: name, 51 | arguments: arguments, 52 | builder: () => GradientText( 53 | key: asT( 54 | safeArguments['key'], 55 | ), 56 | ), 57 | routeName: 'GradientText', 58 | description: 'quickly build gradient text', 59 | ); 60 | case 'fluttercandies://JoinZeroWidthSpace': 61 | return FFRouteSettings( 62 | name: name, 63 | arguments: arguments, 64 | builder: () => JoinZeroWidthSpaceDemo(), 65 | routeName: 'JoinZeroWidthSpace', 66 | description: 67 | 'make line breaking and overflow style better, workaround for issue 18761.', 68 | ); 69 | case 'fluttercandies://RegExpTextDemo': 70 | return FFRouteSettings( 71 | name: name, 72 | arguments: arguments, 73 | builder: () => RegExpTextDemo(), 74 | routeName: 'RegExText', 75 | description: 'quickly build special text with RegExp', 76 | ); 77 | case 'fluttercandies://SearchHighlightDemo': 78 | return FFRouteSettings( 79 | name: name, 80 | arguments: arguments, 81 | builder: () => SearchHighlightDemo( 82 | key: asT( 83 | safeArguments['key'], 84 | ), 85 | ), 86 | routeName: 'SearchHighlightDemo', 87 | description: 88 | 'show how to highlight text when searching. TextOverflowPosition.auto', 89 | ); 90 | case 'fluttercandies://SelectableRegionWithTextFieldDemo': 91 | return FFRouteSettings( 92 | name: name, 93 | arguments: arguments, 94 | builder: () => SelectableRegionWithTextFieldDemo( 95 | key: asT( 96 | safeArguments['key'], 97 | ), 98 | ), 99 | routeName: 'SelectableRegionWithTextField', 100 | description: 'SelectableRegion works with TextField', 101 | ); 102 | case 'fluttercandies://SelectionAreaDemo': 103 | return FFRouteSettings( 104 | name: name, 105 | arguments: arguments, 106 | builder: () => SelectionAreaDemo( 107 | key: asT( 108 | safeArguments['key'], 109 | ), 110 | ), 111 | routeName: 'SelectionArea', 112 | description: 'SelectionArea support', 113 | ); 114 | case 'fluttercandies://TextDemo': 115 | return FFRouteSettings( 116 | name: name, 117 | arguments: arguments, 118 | builder: () => TextDemo(), 119 | routeName: 'Text', 120 | description: 'quickly build special text', 121 | ); 122 | case 'fluttercandies://mainpage': 123 | return FFRouteSettings( 124 | name: name, 125 | arguments: arguments, 126 | builder: () => MainPage(), 127 | routeName: 'MainPage', 128 | ); 129 | default: 130 | return FFRouteSettings( 131 | name: FFRoute.notFoundName, 132 | routeName: FFRoute.notFoundRouteName, 133 | builder: notFoundPageBuilder ?? () => Container(), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /example/lib/example_routes.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY MANUALLY 2 | // ************************************************************************** 3 | // Auto generated by https://github.com/fluttercandies/ff_annotation_route 4 | // ************************************************************************** 5 | // fast mode: true 6 | // version: 10.1.0 7 | // ************************************************************************** 8 | // ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import,unnecessary_import,unused_shown_name,implementation_imports,duplicate_import,library_private_types_in_public_api 9 | /// The routeNames auto generated by https://github.com/fluttercandies/ff_annotation_route 10 | const List routeNames = [ 11 | 'fluttercandies://BackgroundTextDemo', 12 | 'fluttercandies://CustomTextOverflowDemo', 13 | 'fluttercandies://GradientText', 14 | 'fluttercandies://JoinZeroWidthSpace', 15 | 'fluttercandies://RegExpTextDemo', 16 | 'fluttercandies://SearchHighlightDemo', 17 | 'fluttercandies://SelectableRegionWithTextFieldDemo', 18 | 'fluttercandies://SelectionAreaDemo', 19 | 'fluttercandies://TextDemo', 20 | 'fluttercandies://mainpage', 21 | ]; 22 | 23 | /// The routes auto generated by https://github.com/fluttercandies/ff_annotation_route 24 | class Routes { 25 | const Routes._(); 26 | 27 | /// 'workaround for issue 24335/24337 about background' 28 | /// 29 | /// [name] : 'fluttercandies://BackgroundTextDemo' 30 | /// 31 | /// [routeName] : 'BackgroundText' 32 | /// 33 | /// [description] : 'workaround for issue 24335/24337 about background' 34 | static const String fluttercandiesBackgroundTextDemo = 35 | 'fluttercandies://BackgroundTextDemo'; 36 | 37 | /// 'workaround for issue 26748. how to custom text overflow' 38 | /// 39 | /// [name] : 'fluttercandies://CustomTextOverflowDemo' 40 | /// 41 | /// [routeName] : 'CustomTextOverflow' 42 | /// 43 | /// [description] : 'workaround for issue 26748. how to custom text overflow' 44 | static const String fluttercandiesCustomTextOverflowDemo = 45 | 'fluttercandies://CustomTextOverflowDemo'; 46 | 47 | /// 'quickly build gradient text' 48 | /// 49 | /// [name] : 'fluttercandies://GradientText' 50 | /// 51 | /// [routeName] : 'GradientText' 52 | /// 53 | /// [description] : 'quickly build gradient text' 54 | static const String fluttercandiesGradientText = 55 | 'fluttercandies://GradientText'; 56 | 57 | /// 'make line breaking and overflow style better, workaround for issue 18761.' 58 | /// 59 | /// [name] : 'fluttercandies://JoinZeroWidthSpace' 60 | /// 61 | /// [routeName] : 'JoinZeroWidthSpace' 62 | /// 63 | /// [description] : 'make line breaking and overflow style better, workaround for issue 18761.' 64 | static const String fluttercandiesJoinZeroWidthSpace = 65 | 'fluttercandies://JoinZeroWidthSpace'; 66 | 67 | /// 'quickly build special text with RegExp' 68 | /// 69 | /// [name] : 'fluttercandies://RegExpTextDemo' 70 | /// 71 | /// [routeName] : 'RegExText' 72 | /// 73 | /// [description] : 'quickly build special text with RegExp' 74 | static const String fluttercandiesRegExpTextDemo = 75 | 'fluttercandies://RegExpTextDemo'; 76 | 77 | /// 'show how to highlight text when searching. TextOverflowPosition.auto' 78 | /// 79 | /// [name] : 'fluttercandies://SearchHighlightDemo' 80 | /// 81 | /// [routeName] : 'SearchHighlightDemo' 82 | /// 83 | /// [description] : 'show how to highlight text when searching. TextOverflowPosition.auto' 84 | static const String fluttercandiesSearchHighlightDemo = 85 | 'fluttercandies://SearchHighlightDemo'; 86 | 87 | /// 'SelectableRegion works with TextField' 88 | /// 89 | /// [name] : 'fluttercandies://SelectableRegionWithTextFieldDemo' 90 | /// 91 | /// [routeName] : 'SelectableRegionWithTextField' 92 | /// 93 | /// [description] : 'SelectableRegion works with TextField' 94 | static const String fluttercandiesSelectableRegionWithTextFieldDemo = 95 | 'fluttercandies://SelectableRegionWithTextFieldDemo'; 96 | 97 | /// 'SelectionArea support' 98 | /// 99 | /// [name] : 'fluttercandies://SelectionAreaDemo' 100 | /// 101 | /// [routeName] : 'SelectionArea' 102 | /// 103 | /// [description] : 'SelectionArea support' 104 | static const String fluttercandiesSelectionAreaDemo = 105 | 'fluttercandies://SelectionAreaDemo'; 106 | 107 | /// 'quickly build special text' 108 | /// 109 | /// [name] : 'fluttercandies://TextDemo' 110 | /// 111 | /// [routeName] : 'Text' 112 | /// 113 | /// [description] : 'quickly build special text' 114 | static const String fluttercandiesTextDemo = 'fluttercandies://TextDemo'; 115 | 116 | /// 'MainPage' 117 | /// 118 | /// [name] : 'fluttercandies://mainpage' 119 | /// 120 | /// [routeName] : 'MainPage' 121 | /// 122 | /// [constructors] : 123 | /// 124 | /// MainPage : [] 125 | static const String fluttercandiesMainpage = 'fluttercandies://mainpage'; 126 | } 127 | -------------------------------------------------------------------------------- /example/lib/generated_plugin_registrant.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // ignore_for_file: directives_ordering 6 | // ignore_for_file: lines_longer_than_80_chars 7 | // ignore_for_file: depend_on_referenced_packages 8 | 9 | import 'package:url_launcher_web/url_launcher_web.dart'; 10 | 11 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 12 | 13 | // ignore: public_member_api_docs 14 | void registerPlugins(Registrar registrar) { 15 | UrlLauncherPlugin.registerWith(registrar); 16 | registrar.registerMessageHandler(); 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:oktoast/oktoast.dart'; 5 | import 'example_route.dart'; 6 | import 'example_routes.dart'; 7 | 8 | void main() => runApp(MyApp()); 9 | 10 | class MyApp extends StatelessWidget { 11 | // This widget is the root of your application. 12 | @override 13 | Widget build(BuildContext context) { 14 | return OKToast( 15 | child: MaterialApp( 16 | title: 'ExtendedText', 17 | debugShowCheckedModeBanner: false, 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | ), 21 | builder: (BuildContext c, Widget? w) { 22 | // ScreenUtil.instance = 23 | // ScreenUtil(width: 750, height: 1334, allowFontScaling: true) 24 | // ..init(c); 25 | if (!kIsWeb) { 26 | final MediaQueryData data = MediaQuery.of(c); 27 | return MediaQuery( 28 | data: data.copyWith(textScaler: TextScaler.noScaling), 29 | child: w!, 30 | ); 31 | } 32 | return w!; 33 | }, 34 | initialRoute: Routes.fluttercandiesMainpage, 35 | onGenerateRoute: (RouteSettings settings) { 36 | return onGenerateRoute( 37 | settings: settings, 38 | getRouteSettings: getRouteSettings, 39 | ); 40 | }, 41 | )); 42 | } 43 | } 44 | 45 | List? _imageTestUrls; 46 | List get imageTestUrls => 47 | _imageTestUrls ?? 48 | ['https://photo.tuchong.com/4870004/f/298584322.jpg']; 49 | -------------------------------------------------------------------------------- /example/lib/pages/custom_text_overflow_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_special_text_span_builder.dart'; 2 | import 'package:example/text/selection_area.dart'; 3 | import 'package:extended_text/extended_text.dart'; 4 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | @FFRoute( 9 | name: 'fluttercandies://CustomTextOverflowDemo', 10 | routeName: 'CustomTextOverflow', 11 | description: 'workaround for issue 26748. how to custom text overflow') 12 | class CustomTextOverflowDemo extends StatefulWidget { 13 | @override 14 | _CustomTextOverflowDemoState createState() => _CustomTextOverflowDemoState(); 15 | } 16 | 17 | class _CustomTextOverflowDemoState extends State { 18 | final String content = '' 19 | 'relate to \$issue 26748\$ .[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 20 | 'It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 21 | '1234567 if you meet any problem, please let me know @zmtzawqlp .'; 22 | final MySpecialTextSpanBuilder builder = MySpecialTextSpanBuilder(); 23 | bool _joinZeroWidthSpace = false; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: const Text('custom text over flow'), 30 | actions: [ 31 | IconButton( 32 | icon: const Icon(Icons.style), 33 | onPressed: () { 34 | setState(() { 35 | _joinZeroWidthSpace = !_joinZeroWidthSpace; 36 | }); 37 | }) 38 | ], 39 | ), 40 | body: Container( 41 | padding: const EdgeInsets.all(20.0), 42 | child: SingleChildScrollView( 43 | child: Column( 44 | mainAxisAlignment: MainAxisAlignment.start, 45 | crossAxisAlignment: CrossAxisAlignment.start, 46 | children: [ 47 | _buildText(maxLines: null, title: 'Full Text'), 48 | _buildText(position: TextOverflowPosition.end), 49 | _buildText(position: TextOverflowPosition.start), 50 | _buildText(position: TextOverflowPosition.middle), 51 | _buildText(position: TextOverflowPosition.middle, maxLines: 3), 52 | ], 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | 59 | Widget _buildText({ 60 | TextOverflowPosition position = TextOverflowPosition.end, 61 | int? maxLines = 4, 62 | String? title, 63 | }) { 64 | return Card( 65 | child: Padding( 66 | padding: const EdgeInsets.all(8.0), 67 | child: Column( 68 | mainAxisAlignment: MainAxisAlignment.start, 69 | crossAxisAlignment: CrossAxisAlignment.start, 70 | children: [ 71 | Text( 72 | title ?? 73 | 'position: ${position.toString().replaceAll('TextOverflowPosition.', '')}${maxLines != null ? ' , maxLines: $maxLines' : ''}', 74 | style: const TextStyle(fontWeight: FontWeight.bold), 75 | ), 76 | const Padding( 77 | padding: EdgeInsets.symmetric(vertical: 10), 78 | child: Divider( 79 | height: 1, 80 | color: Colors.grey, 81 | ), 82 | ), 83 | CommonSelectionArea( 84 | // if betterLineBreakingAndOverflowStyle is true, you must take care of copy text. 85 | // override [TextSelectionControls.handleCopy], remove zero width space. 86 | joinZeroWidthSpace: _joinZeroWidthSpace, 87 | child: ExtendedText( 88 | content, 89 | onSpecialTextTap: onSpecialTextTap, 90 | specialTextSpanBuilder: builder, 91 | joinZeroWidthSpace: _joinZeroWidthSpace, 92 | overflowWidget: TextOverflowWidget( 93 | position: position, 94 | align: TextOverflowAlign.center, 95 | // just for debug 96 | debugOverflowRectColor: Colors.red.withOpacity(0.1), 97 | child: Container( 98 | //color: Colors.yellow, 99 | child: SelectionContainer.disabled( 100 | child: Row( 101 | mainAxisSize: MainAxisSize.min, 102 | children: [ 103 | const Text('\u2026 '), 104 | InkWell( 105 | child: const Text( 106 | 'more', 107 | ), 108 | onTap: () { 109 | launchUrl(Uri.parse( 110 | 'https://github.com/fluttercandies/extended_text')); 111 | }, 112 | ) 113 | ], 114 | ), 115 | ), 116 | ), 117 | ), 118 | maxLines: maxLines, 119 | ), 120 | ), 121 | ], 122 | )), 123 | ); 124 | } 125 | 126 | void onSpecialTextTap(dynamic parameter) { 127 | if (parameter.toString().startsWith('\$')) { 128 | if (parameter.toString().contains('issue')) { 129 | launchUrl(Uri.parse('https://github.com/flutter/flutter/issues/26748')); 130 | } else { 131 | launchUrl(Uri.parse('https://github.com/fluttercandies')); 132 | } 133 | } else if (parameter.toString().startsWith('@')) { 134 | launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /example/lib/pages/join_zero_width_space.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_special_text_span_builder.dart'; 2 | import 'package:example/text/selection_area.dart'; 3 | import 'package:extended_text/extended_text.dart'; 4 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | @FFRoute( 9 | name: 'fluttercandies://JoinZeroWidthSpace', 10 | routeName: 'JoinZeroWidthSpace', 11 | description: 12 | 'make line breaking and overflow style better, workaround for issue 18761.') 13 | class JoinZeroWidthSpaceDemo extends StatelessWidget { 14 | final String content = 15 | 'relate to \$issue 26748\$ .[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 16 | 'It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 17 | '1234567 if you meet any problem, please let me know @zmtzawqlp .'; 18 | final MySpecialTextSpanBuilder builder = MySpecialTextSpanBuilder(); 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: const Text('Join Zero-Width Space'), 24 | ), 25 | body: Container( 26 | padding: const EdgeInsets.all(20.0), 27 | child: SingleChildScrollView( 28 | child: Column( 29 | mainAxisAlignment: MainAxisAlignment.start, 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | _buildText( 33 | joinZeroWidthSpace: false, 34 | ), 35 | _buildText( 36 | joinZeroWidthSpace: true, 37 | ), 38 | ], 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | 45 | Widget _buildText({ 46 | int? maxLines = 4, 47 | String? title, 48 | bool joinZeroWidthSpace = false, 49 | }) { 50 | return Card( 51 | child: Padding( 52 | padding: const EdgeInsets.all(8.0), 53 | child: Column( 54 | mainAxisAlignment: MainAxisAlignment.start, 55 | crossAxisAlignment: CrossAxisAlignment.start, 56 | children: [ 57 | Text( 58 | title ?? 'joinZeroWidthSpace: $joinZeroWidthSpace', 59 | style: const TextStyle(fontWeight: FontWeight.bold), 60 | ), 61 | const Padding( 62 | padding: EdgeInsets.symmetric(vertical: 10), 63 | child: Divider( 64 | height: 1, 65 | color: Colors.grey, 66 | ), 67 | ), 68 | CommonSelectionArea( 69 | // if betterLineBreakingAndOverflowStyle is true, you must take care of copy text. 70 | // override [TextSelectionControls.handleCopy], remove zero width space. 71 | joinZeroWidthSpace: joinZeroWidthSpace, 72 | child: ExtendedText( 73 | content, 74 | onSpecialTextTap: onSpecialTextTap, 75 | specialTextSpanBuilder: builder, 76 | joinZeroWidthSpace: joinZeroWidthSpace, 77 | overflow: TextOverflow.ellipsis, 78 | maxLines: maxLines, 79 | ), 80 | ), 81 | ], 82 | )), 83 | ); 84 | } 85 | 86 | void onSpecialTextTap(dynamic parameter) { 87 | if (parameter.toString().startsWith('\$')) { 88 | if (parameter.toString().contains('issue')) { 89 | launchUrl(Uri.parse('https://github.com/flutter/flutter/issues/26748')); 90 | } else { 91 | launchUrl(Uri.parse('https://github.com/fluttercandies')); 92 | } 93 | } else if (parameter.toString().startsWith('@')) { 94 | launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | 6 | import '../example_route.dart'; 7 | import '../example_routes.dart' as example_routes; 8 | 9 | @FFRoute( 10 | name: 'fluttercandies://mainpage', 11 | routeName: 'MainPage', 12 | ) 13 | class MainPage extends StatelessWidget { 14 | MainPage() { 15 | final List routeNames = []; 16 | routeNames.addAll(example_routes.routeNames); 17 | routeNames.remove('fluttercandies://picswiper'); 18 | routeNames.remove('fluttercandies://mainpage'); 19 | routes.addAll(routeNames 20 | .map((String name) => getRouteSettings(name: name))); 21 | } 22 | final List routes = []; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: AppBar( 28 | // Here we take the value from the MyHomePage object that was created by 29 | // the App.build method, and use it to set our appbar title. 30 | title: const Text('ExtendedText'), 31 | actions: [ 32 | ButtonTheme( 33 | minWidth: 0.0, 34 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 35 | child: TextButton( 36 | child: const Text( 37 | 'Github', 38 | style: TextStyle( 39 | decorationStyle: TextDecorationStyle.solid, 40 | decoration: TextDecoration.underline, 41 | color: Colors.white, 42 | ), 43 | ), 44 | onPressed: () { 45 | launchUrl(Uri.parse( 46 | 'https://github.com/fluttercandies/extended_text')); 47 | }, 48 | ), 49 | ), 50 | if (!kIsWeb) 51 | ButtonTheme( 52 | padding: const EdgeInsets.only(right: 10.0), 53 | minWidth: 0.0, 54 | child: TextButton( 55 | child: Image.network( 56 | 'https://pub.idqqimg.com/wpa/images/group.png'), 57 | onPressed: () { 58 | launchUrl(Uri.parse('https://jq.qq.com/?_wv=1027&k=5bcc0gy')); 59 | }, 60 | ), 61 | ) 62 | ], 63 | ), 64 | body: ListView.builder( 65 | itemBuilder: (BuildContext c, int index) { 66 | final FFRouteSettings page = routes[index]; 67 | return Container( 68 | margin: const EdgeInsets.all(20.0), 69 | child: GestureDetector( 70 | behavior: HitTestBehavior.translucent, 71 | child: Column( 72 | crossAxisAlignment: CrossAxisAlignment.start, 73 | children: [ 74 | Text( 75 | (index + 1).toString() + '.' + page.routeName!, 76 | //style: TextStyle(inherit: false), 77 | ), 78 | Text( 79 | page.description!, 80 | style: const TextStyle(color: Colors.grey), 81 | ) 82 | ], 83 | ), 84 | onTap: () { 85 | Navigator.pushNamed(context, routes[index].name!); 86 | }, 87 | )); 88 | }, 89 | itemCount: routes.length, 90 | ), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/lib/pages/regexp_text_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/regexp_special_text_span_builder.dart'; 2 | import 'package:extended_text/extended_text.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | @FFRoute( 8 | name: 'fluttercandies://RegExpTextDemo', 9 | routeName: 'RegExText', 10 | description: 'quickly build special text with RegExp') 11 | class RegExpTextDemo extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text('quickly build special text'), 17 | ), 18 | body: Container( 19 | padding: const EdgeInsets.all(20.0), 20 | child: ExtendedText( 21 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 22 | '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 23 | '\n\nif you meet any problem, please let me know @zmtzawqlp and send an mailto:zmtzawqlp@live.com to me .[sun_glasses] ', 24 | onSpecialTextTap: (dynamic parameter) { 25 | if (parameter.toString().startsWith('\$')) { 26 | launchUrl(Uri.parse('https://github.com/fluttercandies')); 27 | } else if (parameter.toString().startsWith('@')) { 28 | launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 29 | } else if (parameter.toString().startsWith('mailto:')) { 30 | launchUrl(Uri.parse(parameter.toString())); 31 | } 32 | }, 33 | specialTextSpanBuilder: MyRegExpSpecialTextSpanBuilder(), 34 | overflow: TextOverflow.ellipsis, 35 | //style: TextStyle(background: Paint()..color = Colors.red), 36 | maxLines: 10, 37 | ), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/pages/search_highlight_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/highlight_text_span_builder.dart'; 2 | import 'package:extended_text/extended_text.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | @FFRoute( 7 | name: 'fluttercandies://SearchHighlightDemo', 8 | routeName: 'SearchHighlightDemo', 9 | description: 10 | 'show how to highlight text when searching. TextOverflowPosition.auto', 11 | ) 12 | class SearchHighlightDemo extends StatefulWidget { 13 | const SearchHighlightDemo({super.key}); 14 | 15 | @override 16 | State createState() => _SearchHighlightDemoState(); 17 | } 18 | 19 | class _SearchHighlightDemoState extends State { 20 | List searchMessages = [ 21 | ...messages, 22 | ]; 23 | String searchText = ''; 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: AppBar( 28 | title: const Text('SearchHighlightDemo'), 29 | ), 30 | body: Container( 31 | padding: const EdgeInsets.all(20.0), 32 | child: Column( 33 | children: [ 34 | TextField( 35 | onChanged: (String value) { 36 | searchText = value; 37 | setState( 38 | () { 39 | searchMessages.clear(); 40 | if (value.isEmpty) { 41 | searchMessages.addAll(messages); 42 | } else { 43 | final RegExp regex = 44 | RegExp(value, caseSensitive: false); 45 | for (final String message in messages) { 46 | if (message 47 | .toLowerCase() 48 | .contains(value.toLowerCase())) { 49 | final RegExpMatch? match = 50 | regex.firstMatch(message); 51 | if (match != null) { 52 | final String highlightedMessage = 53 | message.replaceFirst( 54 | regex, 55 | HighlightText.getHighlightString( 56 | match.group(0)!), 57 | ); 58 | searchMessages.add(highlightedMessage); 59 | } 60 | } 61 | } 62 | } 63 | }, 64 | ); 65 | }, 66 | ), 67 | Expanded( 68 | child: ListView.builder( 69 | itemBuilder: (BuildContext context, int index) { 70 | return GestureDetector( 71 | onTap: () { 72 | showDialog( 73 | context: context, 74 | builder: (BuildContext b) { 75 | return AlertDialog( 76 | title: const Text('FullText'), 77 | content: ExtendedText( 78 | searchMessages[index], 79 | specialTextSpanBuilder: 80 | HighlightTextSpanBuilder(), 81 | ), 82 | actions: [ 83 | TextButton( 84 | onPressed: () { 85 | Navigator.pop(b); 86 | }, 87 | child: const Text('OK')) 88 | ], 89 | ); 90 | }); 91 | }, 92 | child: Container( 93 | padding: const EdgeInsets.all(5), 94 | margin: const EdgeInsets.all(10), 95 | decoration: BoxDecoration(border: Border.all()), 96 | child: ExtendedText( 97 | searchMessages[index], 98 | specialTextSpanBuilder: HighlightTextSpanBuilder(), 99 | maxLines: searchText.isEmpty ? 3 : 1, 100 | overflowWidget: TextOverflowWidget( 101 | child: const Text('\u2026 '), 102 | // debugOverflowRectColor: Colors.red.withOpacity(0.1), 103 | position: searchText.isEmpty 104 | ? TextOverflowPosition.end 105 | : TextOverflowPosition.auto, 106 | ), 107 | ), 108 | ), 109 | ); 110 | }, 111 | itemCount: searchMessages.length, 112 | ), 113 | ), 114 | ], 115 | )), 116 | ); 117 | } 118 | } 119 | 120 | const List messages = [ 121 | '【翼支付】尊敬的用户,您有2元话费券未使用,将于5天内失效,点击查看,如已使用请忽略!拒收请回复R', 122 | '气象台下周天气预报:17日阴到多云有短时小雨转多云到阴15到18度;18日阴到多云,局部有短时小雨转阴到多云12到15度;19日多云到阴转多云12到15度;20日多云12到17度;21日多云到晴13到17度;22日多云12到17度;23日阴到多云转阴到多云有短时小雨13到18度。【中国移动 气象助手】', 123 | '气象台15日6时:阴到多云有时有阵雨,今上午以前大部地区有雾。东北风3-4级,明转偏北风4-5级。23-19度。我台已发布大雾黄色预警。【中国移动 气象助手】', 124 | '防汛防台安全提示:“贝碧嘉”将近,请关注天气;暴雨来临,减少出行;确需开车,遇水绕行;注意坠物,减少伤害;人人关注防汛、人人知晓防汛、人人参与防汛。【市防汛指挥部办公室】', 125 | '市通管局、市反诈中心提醒:警惕邮寄黄金诈骗。近期,诈骗分子以各种名义,诱骗群众购买并邮寄实物黄金的案件呈上升趋势,请广大市民群众谨防被骗。', 126 | '【中国电信积分商城】尊敬的用户,您的 爱奇艺 VIP会员黄金月卡 已充值成功,使用充值账号登录即可享受会员权益。如尚未注册,使用充值号码完成注册后登录即可。关注微信“天翼积分”公众号 ,在个人中心查看订单详情!', 127 | '【人口普查】依法配合人口普查是每个公民应尽的义务。10月11日起,本市普查指导员和普查员将佩戴统一证件入户开展普查摸底,需要您的支持和配合!', 128 | '上海海警局提醒您:5月1日起,上海海域进入海洋伏季休渔期。请自觉遵守伏季休渔制度,切勿在通信海缆保护区内从事挖砂、钻探、抛锚、拖锚、底拖捕捞、张网及其他可能危及通信海缆安全的海上作业,积极配合执法部门开展日常执法工作,切实保护海底电缆管道及海洋渔业资源。欢迎通过95110海上报警电话提供违法违规线索。', 129 | '【开放原子】您好,第二届开放原子大赛已正式启动,大赛覆盖基础软件、工业软件、人工智能大模型、创新应用等多个技术领域,设置巅峰挑战赛、实战竞技赛、训练学习赛等不同难度的赛项类型,总奖金约1500万元。登录大赛官网,查看更多比赛信息。拒收请回复R', 130 | '【Apple】Apple 账户代码为:117409。请勿与他人共享。', 131 | '【饿了么】您在:炭小签·贵阳特色烧烤下的订单正在加急调度骑士中,恳请您耐心等待!', 132 | ]; 133 | -------------------------------------------------------------------------------- /example/lib/pages/selectable_region_width_text_field_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_special_text_span_builder.dart'; 2 | import 'package:extended_text/extended_text.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | @FFRoute( 8 | name: 'fluttercandies://SelectableRegionWithTextFieldDemo', 9 | routeName: 'SelectableRegionWithTextField', 10 | description: 'SelectableRegion works with TextField', 11 | ) 12 | class SelectableRegionWithTextFieldDemo extends StatefulWidget { 13 | const SelectableRegionWithTextFieldDemo({super.key}); 14 | 15 | @override 16 | State createState() => 17 | _SelectableRegionWithTextFieldDemoState(); 18 | } 19 | 20 | class _SelectableRegionWithTextFieldDemoState 21 | extends State { 22 | final SelectableRegionFocusNode _myFocusNode = SelectableRegionFocusNode(); 23 | 24 | final GlobalKey _key = 25 | GlobalKey(); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | final TextSelectionControls controls = switch (Theme.of(context).platform) { 30 | TargetPlatform.android || 31 | TargetPlatform.fuchsia => 32 | materialTextSelectionHandleControls, 33 | TargetPlatform.linux || 34 | TargetPlatform.windows => 35 | desktopTextSelectionHandleControls, 36 | TargetPlatform.iOS => cupertinoTextSelectionHandleControls, 37 | TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls, 38 | }; 39 | const String content = 40 | '中文 [love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 41 | 'It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 42 | 'if you meet any problem, please let me know @zmtzawqlp .[sun_glasses]'; 43 | return Scaffold( 44 | appBar: AppBar( 45 | title: const Text('SelectionArea Support'), 46 | ), 47 | body: Container( 48 | alignment: Alignment.center, 49 | margin: const EdgeInsets.all(20.0), 50 | child: Column( 51 | children: [ 52 | TextButton( 53 | onPressed: () { 54 | _myFocusNode.hideMenu(); 55 | }, 56 | child: const Text('hide menu'), 57 | ), 58 | SelectableRegion( 59 | key: _key, 60 | child: GestureDetector( 61 | child: ExtendedText( 62 | content, 63 | specialTextSpanBuilder: MySpecialTextSpanBuilder(), 64 | ), 65 | behavior: HitTestBehavior.translucent, 66 | onLongPress: () { 67 | _key.currentState?.selectAll(SelectionChangedCause.toolbar); 68 | }, 69 | ), 70 | focusNode: _myFocusNode, 71 | selectionControls: controls, 72 | contextMenuBuilder: (BuildContext context, 73 | SelectableRegionState selectableRegionState) { 74 | return AdaptiveTextSelectionToolbar.selectableRegion( 75 | selectableRegionState: selectableRegionState, 76 | ); 77 | }, 78 | ), 79 | const Spacer(), 80 | const TextField( 81 | maxLines: 1, 82 | style: TextStyle(height: 1), 83 | strutStyle: StrutStyle( 84 | height: 2.0, 85 | forceStrutHeight: true, 86 | ), 87 | ) 88 | ], 89 | ), 90 | ), 91 | ); 92 | } 93 | } 94 | 95 | /// SelectableRegion don't want to hide menu when TextFiled get focus 96 | /// And when show menu, we don't want to make TextField lose focus 97 | /// so we need to override FocusNode 98 | class SelectableRegionFocusNode extends FocusNode { 99 | SelectableRegionFocusNode(); 100 | 101 | @override 102 | bool get hasFocus => false; 103 | 104 | @override 105 | bool get hasPrimaryFocus => false; 106 | 107 | @override 108 | void requestFocus([FocusNode? node]) { 109 | return; 110 | } 111 | 112 | void hideMenu() { 113 | notifyListeners(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/lib/pages/selection_area_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_special_text_span_builder.dart'; 2 | import 'package:example/text/selection_area.dart'; 3 | import 'package:extended_text/extended_text.dart'; 4 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | @FFRoute( 9 | name: 'fluttercandies://SelectionAreaDemo', 10 | routeName: 'SelectionArea', 11 | description: 'SelectionArea support', 12 | ) 13 | class SelectionAreaDemo extends StatelessWidget { 14 | const SelectionAreaDemo({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | const String content = 19 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 20 | 'It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 21 | 'if you meet any problem, please let me know @zmtzawqlp .[sun_glasses]'; 22 | return Scaffold( 23 | appBar: AppBar( 24 | title: const Text('SelectionArea Support'), 25 | ), 26 | body: GestureDetector( 27 | behavior: HitTestBehavior.translucent, 28 | onTap: () { 29 | FocusManager.instance.primaryFocus?.unfocus(); 30 | }, 31 | child: Container( 32 | alignment: Alignment.center, 33 | margin: const EdgeInsets.all(20.0), 34 | child: CommonSelectionArea( 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | crossAxisAlignment: CrossAxisAlignment.center, 38 | children: [ 39 | const Text( 40 | content, 41 | maxLines: 4, 42 | ), 43 | const SizedBox(height: 10), 44 | ExtendedText( 45 | content, 46 | onSpecialTextTap: (dynamic parameter) { 47 | if (parameter.toString().startsWith('\$')) { 48 | launchUrl(Uri.parse('https://github.com/fluttercandies')); 49 | } else if (parameter.toString().startsWith('@')) { 50 | launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 51 | } 52 | }, 53 | specialTextSpanBuilder: MySpecialTextSpanBuilder(), 54 | overflow: TextOverflow.ellipsis, 55 | overflowWidget: TextOverflowWidget( 56 | position: TextOverflowPosition.middle, 57 | align: TextOverflowAlign.center, 58 | // just for debug 59 | debugOverflowRectColor: Colors.red.withOpacity(0.1), 60 | child: Container( 61 | //color: Colors.yellow, 62 | child: 63 | // overwidget text should be not selectable 64 | SelectionContainer.disabled( 65 | child: Row( 66 | mainAxisSize: MainAxisSize.min, 67 | children: [ 68 | const Text('\u2026 '), 69 | InkWell( 70 | child: const Text( 71 | 'more', 72 | ), 73 | onTap: () { 74 | launchUrl(Uri.parse( 75 | 'https://github.com/fluttercandies/extended_text')); 76 | }, 77 | ) 78 | ], 79 | ), 80 | ), 81 | ), 82 | ), 83 | maxLines: 4, 84 | ), 85 | ], 86 | ), 87 | ), 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/pages/text_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_special_text_span_builder.dart'; 2 | import 'package:extended_text/extended_text.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | @FFRoute( 8 | name: 'fluttercandies://TextDemo', 9 | routeName: 'Text', 10 | description: 'quickly build special text') 11 | class TextDemo extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: const Text('quickly build special text'), 17 | ), 18 | body: Container( 19 | padding: const EdgeInsets.all(20.0), 20 | child: ExtendedText( 21 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 22 | '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 23 | '\n\nif you meet any problem, please let me know @zmtzawqlp .[sun_glasses]', 24 | onSpecialTextTap: (dynamic parameter) { 25 | if (parameter.toString().startsWith('\$')) { 26 | launchUrl(Uri.parse('https://github.com/fluttercandies')); 27 | } else if (parameter.toString().startsWith('@')) { 28 | launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 29 | } 30 | }, 31 | specialTextSpanBuilder: MySpecialTextSpanBuilder(), 32 | overflow: TextOverflow.ellipsis, 33 | //style: TextStyle(background: Paint()..color = Colors.red), 34 | maxLines: 10, 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/pages/text_selection_demo.dart: -------------------------------------------------------------------------------- 1 | // /// 2 | // /// photo_view_demo.dart 3 | // /// create by zmtzawqlp on 2019/4/4 4 | // /// 5 | 6 | // // ignore_for_file: always_put_control_body_on_new_line 7 | 8 | // import 'package:example/text/my_extended_text_selection_controls.dart'; 9 | // import 'package:example/text/my_special_text_span_builder.dart'; 10 | // import 'package:extended_text/extended_text.dart'; 11 | // import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 12 | // import 'package:flutter/material.dart' hide CircularProgressIndicator; 13 | // import 'package:url_launcher/url_launcher.dart'; 14 | 15 | // @FFRoute( 16 | // name: 'fluttercandies://TextSelectionDemo', 17 | // routeName: 'TextSelection', 18 | // description: 'text selection support') 19 | // class TextSelectionDemo extends StatefulWidget { 20 | // @override 21 | // _TextSelectionDemoState createState() => _TextSelectionDemoState(); 22 | // } 23 | 24 | // class _TextSelectionDemoState extends State { 25 | // late TextSelectionControls _myTextSelectionControls; 26 | // final String _attachContent = 27 | // '[love]Extended text help you to build rich text quickly. any special text you will have with extended text.It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love] if you meet any problem, please let me know @zmtzawqlp .[sun_glasses]'; 28 | // @override 29 | // void initState() { 30 | // super.initState(); 31 | // _myTextSelectionControls = MyTextSelectionControls(); 32 | // } 33 | 34 | // @override 35 | // Widget build(BuildContext context) { 36 | // final Widget result = Material( 37 | // child: Column( 38 | // children: [ 39 | // AppBar( 40 | // title: const Text('text selection support'), 41 | // ), 42 | // Expanded( 43 | // child: ListView.builder( 44 | // itemBuilder: (BuildContext context, int index) { 45 | // //return SelectableText(_attachContent); 46 | 47 | // return Padding( 48 | // padding: const EdgeInsets.all(20), 49 | // child: ExtendedText( 50 | // _attachContent, 51 | // onSpecialTextTap: (dynamic parameter) { 52 | // if (parameter.toString().startsWith('\$')) { 53 | // launchUrl( 54 | // Uri.parse('https://github.com/fluttercandies')); 55 | // } else if (parameter.toString().startsWith('@')) { 56 | // launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); 57 | // } 58 | // }, 59 | // specialTextSpanBuilder: MySpecialTextSpanBuilder(), 60 | // //overflow: ExtendedTextOverflow.ellipsis, 61 | // style: const TextStyle(fontSize: 14, color: Colors.grey), 62 | // maxLines: 4, 63 | // overflowWidget: TextOverflowWidget( 64 | // child: Row( 65 | // mainAxisSize: MainAxisSize.min, 66 | // children: [ 67 | // const Text('\u2026 '), 68 | // InkWell( 69 | // child: const Text('more'), 70 | // onTap: () { 71 | // launchUrl(Uri.parse( 72 | // 'https://github.com/fluttercandies/extended_text')); 73 | // }, 74 | // ) 75 | // ], 76 | // ), 77 | // ), 78 | // selectionEnabled: true, 79 | // selectionControls: _myTextSelectionControls, 80 | // shouldShowSelectionHandles: _shouldShowSelectionHandles, 81 | // textSelectionGestureDetectorBuilder: ({ 82 | // required ExtendedTextSelectionGestureDetectorBuilderDelegate 83 | // delegate, 84 | // required Function showToolbar, 85 | // required Function hideToolbar, 86 | // required Function? onTap, 87 | // required BuildContext context, 88 | // required Function? requestKeyboard, 89 | // }) { 90 | // return MyCommonTextSelectionGestureDetectorBuilder( 91 | // delegate: delegate, 92 | // showToolbar: showToolbar, 93 | // hideToolbar: hideToolbar, 94 | // onTap: onTap, 95 | // context: context, 96 | // requestKeyboard: requestKeyboard, 97 | // ); 98 | // }, 99 | // ), 100 | // ); 101 | // }, 102 | // itemCount: 100, 103 | // ), 104 | // ), 105 | // ], 106 | // ), 107 | // ); 108 | 109 | // return ExtendedTextSelectionPointerHandler( 110 | // //default behavior 111 | // // child: result, 112 | // //custom your behavior 113 | // builder: (List states) { 114 | // return Listener( 115 | // child: result, 116 | // behavior: HitTestBehavior.translucent, 117 | // onPointerDown: (PointerDownEvent value) { 118 | // for (final ExtendedTextSelectionState state in states) { 119 | // if (!state.containsPosition(value.position)) { 120 | // //clear other selection 121 | // state.clearSelection(); 122 | // } 123 | // } 124 | // }, 125 | // onPointerMove: (PointerMoveEvent value) { 126 | // //clear other selection 127 | // for (final ExtendedTextSelectionState state in states) { 128 | // state.clearSelection(); 129 | // } 130 | // }, 131 | // ); 132 | // }, 133 | // ); 134 | // } 135 | 136 | // bool _shouldShowSelectionHandles( 137 | // SelectionChangedCause? cause, 138 | // CommonTextSelectionGestureDetectorBuilder selectionGestureDetectorBuilder, 139 | // TextEditingValue editingValue, 140 | // ) { 141 | // // When the text field is activated by something that doesn't trigger the 142 | // // selection overlay, we shouldn't show the handles either. 143 | 144 | // // 145 | // // if (!selectionGestureDetectorBuilder.shouldShowSelectionToolbar) 146 | // // return false; 147 | 148 | // if (cause == SelectionChangedCause.keyboard) return false; 149 | 150 | // // if (widget.readOnly && _effectiveController.selection.isCollapsed) 151 | // // return false; 152 | 153 | // // if (!_isEnabled) return false; 154 | 155 | // if (cause == SelectionChangedCause.longPress) return true; 156 | 157 | // if (editingValue.text.isNotEmpty) return true; 158 | 159 | // return false; 160 | // } 161 | // } 162 | 163 | // class MyCommonTextSelectionGestureDetectorBuilder 164 | // extends CommonTextSelectionGestureDetectorBuilder { 165 | // MyCommonTextSelectionGestureDetectorBuilder( 166 | // {required ExtendedTextSelectionGestureDetectorBuilderDelegate delegate, 167 | // required Function showToolbar, 168 | // required Function hideToolbar, 169 | // required Function? onTap, 170 | // required BuildContext context, 171 | // required Function? requestKeyboard}) 172 | // : super( 173 | // delegate: delegate, 174 | // showToolbar: showToolbar, 175 | // hideToolbar: hideToolbar, 176 | // onTap: onTap, 177 | // context: context, 178 | // requestKeyboard: requestKeyboard, 179 | // ); 180 | // @override 181 | // void onTapDown(TapDragDownDetails details) { 182 | // super.onTapDown(details); 183 | 184 | // /// always show toolbar 185 | // shouldShowSelectionToolbar = true; 186 | // } 187 | 188 | // @override 189 | // bool get showToolbarInWeb => true; 190 | // } 191 | -------------------------------------------------------------------------------- /example/lib/text/highlight_text_span_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_text/extended_text.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class HighlightText extends RegExpSpecialText { 5 | @override 6 | RegExp get regExp => RegExp( 7 | "(.*?)", 8 | ); 9 | 10 | static String getHighlightString(String content) { 11 | return '' + content + ''; 12 | } 13 | 14 | @override 15 | InlineSpan finishText(int start, Match match, 16 | {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) { 17 | final String hexColor = match[1]!; 18 | 19 | return SpecialTextSpan( 20 | text: match[2]!, 21 | actualText: match[0], 22 | start: start, 23 | style: textStyle?.copyWith( 24 | color: Color(int.parse(hexColor.substring(1), radix: 16)), 25 | ), 26 | keepVisible: true, 27 | ); 28 | } 29 | } 30 | 31 | class HighlightTextSpanBuilder extends RegExpSpecialTextSpanBuilder { 32 | @override 33 | List get regExps => [ 34 | HighlightText(), 35 | ]; 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/text/my_extended_text_selection_controls.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// 5 | /// create by zmtzawqlp on 2019/8/3 6 | /// 7 | 8 | const double _kHandleSize = 22.0; 9 | 10 | /// Android Material styled text selection controls. 11 | 12 | class MyTextSelectionControls extends TextSelectionControls 13 | with TextSelectionHandleControls { 14 | MyTextSelectionControls({this.joinZeroWidthSpace = false}); 15 | final bool joinZeroWidthSpace; 16 | 17 | /// Returns the size of the Material handle. 18 | @override 19 | Size getHandleSize(double textLineHeight) => 20 | const Size(_kHandleSize, _kHandleSize); 21 | 22 | /// Builder for material-style text selection handles. 23 | @override 24 | Widget buildHandle( 25 | BuildContext context, TextSelectionHandleType type, double textLineHeight, 26 | [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { 27 | final Widget handle = SizedBox( 28 | width: _kHandleSize, 29 | height: _kHandleSize, 30 | child: Image.asset( 31 | 'assets/40.png', 32 | ), 33 | ); 34 | 35 | // [handle] is a circle, with a rectangle in the top left quadrant of that 36 | // circle (an onion pointing to 10:30). We rotate [handle] to point 37 | // straight up or up-right depending on the handle type. 38 | switch (type) { 39 | case TextSelectionHandleType.left: // points up-right 40 | return Transform.rotate( 41 | angle: math.pi / 4.0, 42 | child: handle, 43 | ); 44 | case TextSelectionHandleType.right: // points up-left 45 | return Transform.rotate( 46 | angle: -math.pi / 4.0, 47 | child: handle, 48 | ); 49 | case TextSelectionHandleType.collapsed: // points up 50 | return handle; 51 | } 52 | } 53 | 54 | /// Gets anchor for material-style text selection handles. 55 | /// 56 | /// See [TextSelectionControls.getHandleAnchor]. 57 | @override 58 | Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, 59 | [double? startGlyphHeight, double? endGlyphHeight]) { 60 | switch (type) { 61 | case TextSelectionHandleType.left: 62 | return const Offset(_kHandleSize, 0); 63 | case TextSelectionHandleType.right: 64 | return Offset.zero; 65 | default: 66 | return const Offset(_kHandleSize / 2, -4); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/lib/text/my_special_text_span_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_text/extended_text.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class AtText extends SpecialText { 6 | AtText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, 7 | {required this.start}) 8 | : super(flag, ' ', textStyle, onTap: onTap); 9 | static const String flag = '@'; 10 | final int start; 11 | 12 | @override 13 | InlineSpan finishText() { 14 | final TextStyle? textStyle = (this.textStyle ?? const TextStyle()) 15 | .copyWith(color: Colors.blue, fontSize: 16.0); 16 | 17 | final String atText = toString(); 18 | 19 | return SpecialTextSpan( 20 | text: atText, 21 | actualText: atText, 22 | start: start, 23 | style: textStyle, 24 | recognizer: (TapGestureRecognizer() 25 | ..onTap = () { 26 | if (onTap != null) { 27 | onTap!(atText); 28 | } 29 | }), 30 | mouseCursor: SystemMouseCursors.text, 31 | onEnter: (PointerEnterEvent event) { 32 | print(event); 33 | }, 34 | onExit: (PointerExitEvent event) { 35 | print(event); 36 | }, 37 | ); 38 | } 39 | } 40 | 41 | List atList = [ 42 | '@Nevermore ', 43 | '@Dota2 ', 44 | '@Biglao ', 45 | '@艾莉亚·史塔克 ', 46 | '@丹妮莉丝 ', 47 | '@HandPulledNoodles ', 48 | '@Zmtzawqlp ', 49 | '@FaDeKongJian ', 50 | '@CaiJingLongDaLao ', 51 | ]; 52 | 53 | class DollarText extends SpecialText { 54 | DollarText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, 55 | {this.start}) 56 | : super(flag, flag, textStyle, onTap: onTap); 57 | static const String flag = '\$'; 58 | final int? start; 59 | @override 60 | InlineSpan finishText() { 61 | final String text = getContent(); 62 | 63 | return _SpecialTextSpan( 64 | text: text, 65 | actualText: toString(), 66 | start: start!, 67 | deleteAll: false, 68 | style: (textStyle ?? const TextStyle()) 69 | .copyWith(color: Colors.orange, fontSize: 16), 70 | mouseCursor: SystemMouseCursors.text, 71 | recognizer: TapGestureRecognizer() 72 | ..onTap = () { 73 | if (onTap != null) { 74 | onTap!(toString()); 75 | } 76 | }); 77 | } 78 | } 79 | 80 | class _SpecialTextSpan extends SpecialTextSpan with IgnoreGradientSpan { 81 | _SpecialTextSpan({ 82 | super.style, 83 | required super.text, 84 | super.actualText, 85 | super.start = 0, 86 | super.deleteAll = true, 87 | super.recognizer, 88 | super.children, 89 | super.semanticsLabel, 90 | super.mouseCursor, 91 | super.onEnter, 92 | super.onExit, 93 | }); 94 | 95 | @override 96 | String getSelectedContent(String showText) { 97 | return '${DollarText.flag}$showText${DollarText.flag}'; 98 | } 99 | } 100 | 101 | List dollarList = [ 102 | '\$Dota2\$', 103 | '\$Dota2 Ti9\$', 104 | '\$CN dota best dota\$', 105 | '\$Flutter\$', 106 | '\$CN dev best dev\$', 107 | '\$UWP\$', 108 | '\$Nevermore\$', 109 | '\$FlutterCandies\$', 110 | '\$ExtendedImage\$', 111 | '\$ExtendedText\$', 112 | ]; 113 | 114 | class EmojiText extends SpecialText { 115 | EmojiText(TextStyle? textStyle, {this.start}) 116 | : super(EmojiText.flag, ']', textStyle); 117 | static const String flag = '['; 118 | final int? start; 119 | @override 120 | InlineSpan finishText() { 121 | final String key = toString(); 122 | 123 | /// widget span is not working on web 124 | if (EmojiUitl.instance.emojiMap.containsKey(key)) { 125 | //fontsize id define image height 126 | //size = 30.0/26.0 * fontSize 127 | const double size = 20.0; 128 | 129 | ///fontSize 26 and text height =30.0 130 | //final double fontSize = 26.0; 131 | return ImageSpan( 132 | AssetImage( 133 | EmojiUitl.instance.emojiMap[key]!, 134 | ), 135 | actualText: key, 136 | imageWidth: size, 137 | imageHeight: size, 138 | start: start!, 139 | fit: BoxFit.fill, 140 | margin: const EdgeInsets.only(left: 2.0, top: 2.0, right: 2.0), 141 | alignment: PlaceholderAlignment.middle, 142 | ); 143 | } 144 | 145 | return TextSpan(text: toString(), style: textStyle); 146 | } 147 | } 148 | 149 | class EmojiUitl { 150 | EmojiUitl._() { 151 | _emojiMap['[love]'] = '$_emojiFilePath/love.png'; 152 | _emojiMap['[sun_glasses]'] = '$_emojiFilePath/sun_glasses.png'; 153 | } 154 | 155 | final Map _emojiMap = {}; 156 | 157 | Map get emojiMap => _emojiMap; 158 | 159 | final String _emojiFilePath = 'assets'; 160 | 161 | static EmojiUitl? _instance; 162 | static EmojiUitl get instance => _instance ??= EmojiUitl._(); 163 | } 164 | 165 | class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { 166 | MySpecialTextSpanBuilder(); 167 | 168 | @override 169 | SpecialText? createSpecialText(String flag, 170 | {TextStyle? textStyle, 171 | SpecialTextGestureTapCallback? onTap, 172 | int? index}) { 173 | if (flag == '') { 174 | return null; 175 | } 176 | 177 | // index is end index of start flag, so text start index should be index-(flag.length-1) 178 | if (isStart(flag, AtText.flag)) { 179 | return AtText( 180 | textStyle, 181 | onTap, 182 | start: index! - (AtText.flag.length - 1), 183 | ); 184 | } else if (isStart(flag, EmojiText.flag)) { 185 | return EmojiText(textStyle, start: index! - (EmojiText.flag.length - 1)); 186 | } else if (isStart(flag, DollarText.flag)) { 187 | return DollarText(textStyle, onTap, 188 | start: index! - (DollarText.flag.length - 1)); 189 | } 190 | return null; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /example/lib/text/regexp_special_text_span_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_text_library/extended_text_library.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'my_special_text_span_builder.dart'; 5 | 6 | class MyRegExpSpecialTextSpanBuilder extends RegExpSpecialTextSpanBuilder { 7 | @override 8 | List get regExps => [ 9 | RegExpMailText(), 10 | RegExpDollarText(), 11 | RegExpAtText(), 12 | RegExpEmojiText(), 13 | ]; 14 | } 15 | 16 | class RegExpDollarText extends RegExpSpecialText { 17 | @override 18 | RegExp get regExp => RegExp(r'\$(.+)\$'); 19 | 20 | @override 21 | InlineSpan finishText(int start, Match match, 22 | {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) { 23 | textStyle = textStyle?.copyWith(color: Colors.orange, fontSize: 16.0); 24 | 25 | final String value = '${match[0]}'; 26 | 27 | return SpecialTextSpan( 28 | text: value.replaceAll('\$', ''), 29 | actualText: value, 30 | start: start, 31 | style: textStyle, 32 | recognizer: (TapGestureRecognizer() 33 | ..onTap = () { 34 | if (onTap != null) { 35 | onTap(value); 36 | } 37 | }), 38 | mouseCursor: SystemMouseCursors.text, 39 | onEnter: (PointerEnterEvent event) { 40 | print(event); 41 | }, 42 | onExit: (PointerExitEvent event) { 43 | print(event); 44 | }, 45 | ); 46 | } 47 | } 48 | 49 | class RegExpAtText extends RegExpSpecialText { 50 | @override 51 | RegExp get regExp => RegExp('@[^@ ]+'); 52 | 53 | @override 54 | InlineSpan finishText(int start, Match match, 55 | {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) { 56 | textStyle = textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); 57 | 58 | final String value = '${match[0]}'; 59 | 60 | return SpecialTextSpan( 61 | text: value, 62 | actualText: value, 63 | start: start, 64 | style: textStyle, 65 | recognizer: (TapGestureRecognizer() 66 | ..onTap = () { 67 | if (onTap != null) { 68 | onTap(value); 69 | } 70 | }), 71 | mouseCursor: SystemMouseCursors.text, 72 | onEnter: (PointerEnterEvent event) { 73 | print(event); 74 | }, 75 | onExit: (PointerExitEvent event) { 76 | print(event); 77 | }, 78 | ); 79 | } 80 | } 81 | 82 | class RegExpEmojiText extends RegExpSpecialText { 83 | @override 84 | RegExp get regExp => RegExp(r'\[[^[]+\]'); 85 | 86 | @override 87 | InlineSpan finishText( 88 | int start, 89 | Match match, { 90 | TextStyle? textStyle, 91 | SpecialTextGestureTapCallback? onTap, 92 | }) { 93 | final String key = match.input.substring(match.start, match.end); 94 | 95 | /// widget span is not working on web 96 | if (EmojiUitl.instance.emojiMap.containsKey(key)) { 97 | //fontsize id define image height 98 | //size = 30.0/26.0 * fontSize 99 | const double size = 20.0; 100 | 101 | ///fontSize 26 and text height =30.0 102 | //final double fontSize = 26.0; 103 | return ImageSpan( 104 | AssetImage( 105 | EmojiUitl.instance.emojiMap[key]!, 106 | ), 107 | actualText: key, 108 | imageWidth: size, 109 | imageHeight: size, 110 | start: start, 111 | fit: BoxFit.fill, 112 | margin: const EdgeInsets.only(left: 2.0, top: 2.0, right: 2.0), 113 | alignment: PlaceholderAlignment.middle, 114 | ); 115 | } 116 | 117 | return TextSpan(text: toString(), style: textStyle); 118 | } 119 | } 120 | 121 | class RegExpMailText extends RegExpSpecialText { 122 | @override 123 | RegExp get regExp => RegExp(r'mailto:[^ ]+'); 124 | @override 125 | InlineSpan finishText(int start, Match match, 126 | {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) { 127 | textStyle = textStyle?.copyWith(color: Colors.lightBlue, fontSize: 16.0); 128 | 129 | final String value = '${match[0]}'; 130 | 131 | return ExtendedWidgetSpan( 132 | child: GestureDetector( 133 | child: const Icon( 134 | Icons.email, 135 | size: 16, 136 | ), 137 | onTap: () { 138 | if (onTap != null) { 139 | onTap(value); 140 | } 141 | }), 142 | actualText: value, 143 | start: start, 144 | style: textStyle, 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /example/lib/text/selection_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/text/my_extended_text_selection_controls.dart'; 2 | import 'package:extended_text_library/extended_text_library.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | class CommonSelectionArea extends StatelessWidget { 9 | const CommonSelectionArea({ 10 | super.key, 11 | required this.child, 12 | this.joinZeroWidthSpace = false, 13 | }); 14 | final Widget child; 15 | final bool joinZeroWidthSpace; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | SelectedContent? _selectedContent; 20 | return SelectionArea( 21 | selectionControls: MyTextSelectionControls(), 22 | contextMenuBuilder: 23 | (BuildContext context, SelectableRegionState selectableRegionState) { 24 | return AdaptiveTextSelectionToolbar.buttonItems( 25 | buttonItems: [ 26 | ContextMenuButtonItem( 27 | onPressed: () { 28 | // TODO(zmtzawqlp): how to get Selectable 29 | // and _clearSelection is not public 30 | // https://github.com/flutter/flutter/issues/126980 31 | 32 | // onCopy: () { 33 | // _copy(); 34 | 35 | // // In Android copy should clear the selection. 36 | // switch (defaultTargetPlatform) { 37 | // case TargetPlatform.android: 38 | // case TargetPlatform.fuchsia: 39 | // _clearSelection(); 40 | // case TargetPlatform.iOS: 41 | // hideToolbar(false); 42 | // case TargetPlatform.linux: 43 | // case TargetPlatform.macOS: 44 | // case TargetPlatform.windows: 45 | // hideToolbar(); 46 | // } 47 | // }, 48 | 49 | // if (_selectedContent != null) { 50 | // String content = _selectedContent!.plainText; 51 | // if (joinZeroWidthSpace) { 52 | // content = content.replaceAll(zeroWidthSpace, ''); 53 | // } 54 | 55 | // Clipboard.setData(ClipboardData(text: content)); 56 | // selectableRegionState.hideToolbar(true); 57 | // selectableRegionState._clearSelection(); 58 | // } 59 | 60 | selectableRegionState 61 | .copySelection(SelectionChangedCause.toolbar); 62 | 63 | // remove zeroWidthSpace 64 | if (joinZeroWidthSpace) { 65 | Clipboard.getData('text/plain').then((ClipboardData? value) { 66 | if (value != null) { 67 | // remove zeroWidthSpace 68 | final String? plainText = value.text?.replaceAll( 69 | ExtendedTextLibraryUtils.zeroWidthSpace, ''); 70 | if (plainText != null) { 71 | Clipboard.setData(ClipboardData(text: plainText)); 72 | } 73 | } 74 | }); 75 | } 76 | }, 77 | type: ContextMenuButtonType.copy, 78 | ), 79 | ContextMenuButtonItem( 80 | onPressed: () { 81 | selectableRegionState.selectAll(SelectionChangedCause.toolbar); 82 | }, 83 | type: ContextMenuButtonType.selectAll, 84 | ), 85 | ContextMenuButtonItem( 86 | onPressed: () { 87 | launchUrl(Uri.parse( 88 | 'mailto:xxx@live.com?subject=extended_text_share&body=${_selectedContent?.plainText}')); 89 | selectableRegionState.hideToolbar(); 90 | }, 91 | type: ContextMenuButtonType.custom, 92 | label: 'like', 93 | ), 94 | ], 95 | anchors: selectableRegionState.contextMenuAnchors, 96 | ); 97 | // return AdaptiveTextSelectionToolbar.selectableRegion( 98 | // selectableRegionState: selectableRegionState, 99 | // ); 100 | }, 101 | // magnifierConfiguration: TextMagnifierConfiguration( 102 | // magnifierBuilder: ( 103 | // BuildContext context, 104 | // MagnifierController controller, 105 | // ValueNotifier magnifierInfo, 106 | // ) { 107 | // return TextMagnifier( 108 | // magnifierInfo: magnifierInfo, 109 | // ); 110 | // // switch (defaultTargetPlatform) { 111 | // // case TargetPlatform.iOS: 112 | // // return CupertinoTextMagnifier( 113 | // // controller: controller, 114 | // // magnifierInfo: magnifierInfo, 115 | // // ); 116 | // // case TargetPlatform.android: 117 | // // return TextMagnifier( 118 | // // magnifierInfo: magnifierInfo, 119 | // // ); 120 | // // case TargetPlatform.fuchsia: 121 | // // case TargetPlatform.linux: 122 | // // case TargetPlatform.macOS: 123 | // // case TargetPlatform.windows: 124 | // // return null; 125 | // // } 126 | // }, 127 | // ), 128 | // selectionControls: MyTextSelectionControls(), 129 | onSelectionChanged: (SelectedContent? value) { 130 | print(value?.plainText); 131 | _selectedContent = value; 132 | }, 133 | child: child, 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import url_launcher_macos 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: '>=3.3.0 <4.0.0' 14 | flutter: ">=3.19.0" 15 | dependencies: 16 | cupertino_icons: ^1.0.2 17 | ff_annotation_route_library: ^3.0.0 18 | flutter: 19 | sdk: flutter 20 | oktoast: any 21 | url_launcher: any 22 | dependency_overrides: 23 | extended_text: 24 | path: ../ 25 | # extended_text_library: 26 | # path: ../../extended_text_library 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | # For information on the generic Dart part of this file, see the 33 | # following page: https://www.dartlang.org/tools/pub/pubspec 34 | 35 | # The following section is specific to Flutter. 36 | flutter: 37 | 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | # To add assets to your application, add an assets section, like this: 43 | assets: 44 | - assets/ 45 | 46 | # An image asset can refer to one or more resolution-specific "variants", see 47 | # https://flutter.io/assets-and-images/#resolution-aware. 48 | 49 | # For details regarding adding assets from package dependencies, see 50 | # https://flutter.io/assets-and-images/#from-packages 51 | 52 | # To add custom fonts to your application, add a fonts section here, 53 | # in this "flutter" section. Each entry in this list should have a 54 | # "family" key with the font family name, and a "fonts" key with a 55 | # list giving the asset and other descriptors for the font. For 56 | # example: 57 | # fonts: 58 | # - family: Schyler 59 | # fonts: 60 | # - asset: fonts/Schyler-Regular.ttf 61 | # - asset: fonts/Schyler-Italic.ttf 62 | # style: italic 63 | # - family: Trajan Pro 64 | # fonts: 65 | # - asset: fonts/TrajanPro.ttf 66 | # - asset: fonts/TrajanPro_Bold.ttf 67 | # weight: 700 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.io/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_text/e88863dfb6a1021086f656636062c9db75bc4a02/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 36 | 39 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /extended_text.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/extended_text.dart: -------------------------------------------------------------------------------- 1 | library extended_text; 2 | 3 | export 'package:extended_text_library/extended_text_library.dart'; 4 | export 'src/extended/gradient/gradient_config.dart'; 5 | export 'src/extended/widgets/rich_text.dart'; 6 | export 'src/extended/widgets/text.dart'; 7 | export 'src/extended/widgets/text_overflow_widget.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/extended/gradient/gradient_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Enum to represent different modes of gradient applications on text. 4 | enum GradientRenderMode { 5 | fullText, // Apply gradient to the entire text. 6 | line, // Apply gradient to each line of the text. 7 | selection, // Apply gradient to specific text selections. 8 | word, // Apply gradient to specific text word. 9 | character, // Apply gradient to specific text character. 10 | } 11 | 12 | /// The span will always ignore the gradient 13 | mixin IgnoreGradientSpan on InlineSpan {} 14 | 15 | /// Configuration for applying gradients to text. 16 | class GradientConfig { 17 | /// Creates an instance of [GradientConfig]. 18 | /// 19 | /// [gradient] is the gradient that will be applied to the text. 20 | /// 21 | /// [ignoreWidgetSpan] determines whether `WidgetSpan` elements should be 22 | /// included in the gradient application. By default, widget spans are ignored. 23 | /// 24 | /// [renderMode] specifies how the gradient should be applied to the text. The default 25 | /// is [GradientRenderMode.fullText], meaning the gradient will apply to the entire text. 26 | /// 27 | /// [ignoreRegex] is a regular expression used to exclude certain parts of the text 28 | /// from the gradient effect. For example, it can be used to exclude specific characters 29 | /// or words (like emojis or special symbols) from the gradient application. 30 | /// 31 | /// [beforeDrawGradient] A callback function that is called before the gradient is drawn on the text. 32 | 33 | /// [blendMode] The blend mode to be used when applying the gradient. 34 | /// default: [BlendMode.srcIn] (i.e., the gradient will be applied to the text). 35 | /// It's better to use [BlendMode.srcIn] or [BlendMode.srcATop]. 36 | 37 | GradientConfig({ 38 | required this.gradient, 39 | this.ignoreWidgetSpan = true, 40 | this.renderMode = GradientRenderMode.fullText, 41 | this.ignoreRegex, 42 | this.beforeDrawGradient, 43 | this.blendMode = BlendMode.srcIn, 44 | }); 45 | 46 | /// The gradient to be applied for [ExtendedText] 47 | final Gradient gradient; 48 | 49 | /// Whether the gradient should include `WidgetSpan` elements. 50 | final bool ignoreWidgetSpan; 51 | 52 | /// The mode of gradient application (e.g., full text, per line, or per selection). 53 | final GradientRenderMode renderMode; 54 | 55 | /// It is a regular expression used to match 56 | /// specific parts of the text where the gradient should not be applied. 57 | /// For example, it can be used to exclude certain characters or words 58 | /// (like emoji or special symbols) from the gradient effect. 59 | /// default: [GradientMixin.ignoreRegex] 60 | final RegExp? ignoreRegex; 61 | 62 | /// A callback function that is called before the gradient is drawn on the text. 63 | final void Function( 64 | PaintingContext context, 65 | TextPainter textPainter, 66 | Offset offset, 67 | )? beforeDrawGradient; 68 | 69 | /// The blend mode to be used when applying the gradient. 70 | /// default: [BlendMode.srcIn] (i.e., the gradient will be applied to the text). 71 | /// It's better to use [BlendMode.srcIn] or [BlendMode.srcATop]. 72 | final BlendMode blendMode; 73 | 74 | static RegExp ignoreEmojiRegex = RegExp( 75 | r'[\u{1F600}-\u{1F64F}]|' // Emoticons 76 | r'[\u{1F300}-\u{1F5FF}]|' // Miscellaneous Symbols and Pictographs 77 | r'[\u{1F680}-\u{1F6FF}]|' // Transport and Map Symbols 78 | r'[\u{1F700}-\u{1F77F}]|' // Alchemical Symbols 79 | r'[\u{1F780}-\u{1F7FF}]|' // Geometric Shapes Extended 80 | r'[\u{1F800}-\u{1F8FF}]|' // Supplemental Arrows-C 81 | r'[\u{1F900}-\u{1F9FF}]|' // Supplemental Symbols and Pictographs 82 | r'[\u{1FA00}-\u{1FA6F}]|' // Chess Symbols 83 | r'[\u{1FA70}-\u{1FAFF}]|' // Symbols and Pictographs Extended-A 84 | r'[\u{2600}-\u{26FF}]|' // Miscellaneous Symbols 85 | r'[\u{2700}-\u{27BF}]|' // Dingbats 86 | r'[\u{1F1E6}-\u{1F1FF}]', // Flags (iOS) 87 | unicode: true, 88 | ); 89 | 90 | /// Creates a copy of this [GradientConfig] with the given values. 91 | /// 92 | /// If a parameter is not provided, it retains its current value. 93 | GradientConfig copyWith({ 94 | Gradient? gradient, 95 | bool? ignoreWidgetSpan, 96 | GradientRenderMode? renderMode, 97 | void Function( 98 | PaintingContext context, 99 | TextPainter textPainter, 100 | Offset offset, 101 | )? beforeDrawGradient, 102 | BlendMode? blendMode, 103 | }) { 104 | return GradientConfig( 105 | gradient: gradient ?? this.gradient, 106 | ignoreWidgetSpan: ignoreWidgetSpan ?? this.ignoreWidgetSpan, 107 | renderMode: renderMode ?? this.renderMode, 108 | ignoreRegex: ignoreRegex, 109 | beforeDrawGradient: beforeDrawGradient ?? this.beforeDrawGradient, 110 | blendMode: blendMode ?? this.blendMode, 111 | ); 112 | } 113 | 114 | /// Creates a copy of this [GradientConfig] with the given [ignoreRegex]. 115 | GradientConfig copyWithIgnoreRegex(RegExp? ignoreRegex) { 116 | return GradientConfig( 117 | gradient: gradient, 118 | ignoreWidgetSpan: ignoreWidgetSpan, 119 | renderMode: renderMode, 120 | ignoreRegex: ignoreRegex, 121 | beforeDrawGradient: beforeDrawGradient, 122 | blendMode: blendMode, 123 | ); 124 | } 125 | 126 | @override 127 | bool operator ==(Object other) => 128 | identical(this, other) || 129 | other is GradientConfig && 130 | runtimeType == other.runtimeType && 131 | gradient == other.gradient && 132 | ignoreWidgetSpan == other.ignoreWidgetSpan && 133 | renderMode == other.renderMode && 134 | ignoreRegex == other.ignoreRegex && 135 | beforeDrawGradient == other.beforeDrawGradient; 136 | 137 | @override 138 | int get hashCode => 139 | gradient.hashCode ^ 140 | ignoreWidgetSpan.hashCode ^ 141 | renderMode.hashCode ^ 142 | ignoreRegex.hashCode ^ 143 | beforeDrawGradient.hashCode; 144 | } 145 | -------------------------------------------------------------------------------- /lib/src/extended/gradient/gradient_mixin.dart: -------------------------------------------------------------------------------- 1 | part of 'package:extended_text/src/extended/rendering/paragraph.dart'; 2 | 3 | /// A mixin to apply gradient effects to text rendering. 4 | mixin GradientMixin on _RenderParagraph { 5 | GradientConfig? _gradientConfig; 6 | 7 | /// Configuration for applying gradients to text. 8 | /// 9 | /// [gradient] is the gradient that will be applied to the text. 10 | /// [ignoreWidgetSpan] determines whether `WidgetSpan` elements should be 11 | /// included in the gradient application. By default, widget spans are ignored. 12 | /// [mode] specifies how the gradient should be applied to the text. The default 13 | /// is [GradientRenderMode.fullText], meaning the gradient will apply to the entire text. 14 | /// [ignoreRegex] is a regular expression used to exclude certain parts of the text 15 | /// from the gradient effect. For example, it can be used to exclude specific characters 16 | /// or words (like emojis or special symbols) from the gradient application. 17 | GradientConfig? get gradientConfig => _gradientConfig; 18 | set gradientConfig(GradientConfig? value) { 19 | if (_gradientConfig != value) { 20 | _gradientConfig = value; 21 | markNeedsPaint(); 22 | } 23 | } 24 | 25 | /// Method to draw the gradient on the text based on the selected `GradientType`. 26 | /// and ignore the text base on [ignoreGradientRegex] 27 | void drawGradient(PaintingContext context, Offset offset) { 28 | // save for _ignoreGradient 29 | context.canvas.save(); 30 | _ignoreGradient(context, offset); 31 | 32 | if (_gradientConfig?.beforeDrawGradient != null) { 33 | _gradientConfig!.beforeDrawGradient!(context, _textPainter, offset); 34 | } 35 | 36 | _drawGradient(context, offset); 37 | // restore for _ignoreGradient 38 | context.canvas.restore(); 39 | // restore for _drawGradient 40 | context.canvas.restore(); 41 | } 42 | 43 | /// Method to draw the gradient on the text based on the selected `GradientType`. 44 | void _drawGradient(PaintingContext context, Offset offset) { 45 | if (_gradientConfig != null) { 46 | switch (_gradientConfig!.renderMode) { 47 | // Apply the gradient to the entire text area. 48 | case GradientRenderMode.fullText: 49 | _drawGradientWithRect(offset & size, context); 50 | break; 51 | 52 | // Apply the gradient to each individual line of text. 53 | case GradientRenderMode.line: 54 | _textPainter.computeLineMetrics().forEach((ui.LineMetrics line) { 55 | final Rect rect = Rect.fromLTWH( 56 | 0, 57 | line.baseline - line.ascent, 58 | size.width, 59 | line.ascent + line.descent, 60 | ).shift(offset); 61 | _drawGradientWithRect(rect, context); 62 | }); 63 | break; 64 | 65 | // Apply the gradient to the selected text ranges. 66 | case GradientRenderMode.selection: 67 | _textPainter 68 | .getBoxesForSelection(TextSelection( 69 | baseOffset: 0, 70 | extentOffset: _textPainter.plainText.length, 71 | )) 72 | .forEach((ui.TextBox box) { 73 | final Rect rect = box.toRect().shift(offset); 74 | 75 | _drawGradientWithRect(rect, context); 76 | }); 77 | break; 78 | case GradientRenderMode.character: 79 | final CharacterRange characterRange = 80 | CharacterRange(_textPainter.plainText); 81 | int graphemeStart = 0; 82 | while (characterRange.moveNext()) { 83 | final int graphemeEnd = 84 | graphemeStart + characterRange.current.length; 85 | final List boxes = _textPainter.getBoxesForSelection( 86 | TextSelection( 87 | baseOffset: graphemeStart, extentOffset: graphemeEnd), 88 | ); 89 | for (final ui.TextBox box in boxes) { 90 | final ui.Rect rect = box.toRect().shift(offset); 91 | _drawGradientWithRect(rect, context); 92 | } 93 | graphemeStart = graphemeEnd; 94 | } 95 | 96 | break; 97 | case GradientRenderMode.word: 98 | final String text = _textPainter.plainText; 99 | for (int i = 0; i < text.length; i++) { 100 | final ui.TextRange wordBoundary = 101 | _textPainter.getWordBoundary(TextPosition(offset: i)); 102 | final int start = wordBoundary.start; 103 | final int end = wordBoundary.end; 104 | if (start < end && end <= text.length) { 105 | final List boxes = _textPainter.getBoxesForSelection( 106 | TextSelection(baseOffset: start, extentOffset: end), 107 | ); 108 | 109 | for (final ui.TextBox box in boxes) { 110 | final ui.Rect rect = box.toRect().shift(offset); 111 | _drawGradientWithRect(rect, context); 112 | } 113 | } 114 | i = math.max(i, math.max(start, end - 1)); 115 | } 116 | break; 117 | } 118 | } 119 | } 120 | 121 | bool _ignoreGradient(PaintingContext context, ui.Offset offset) { 122 | final List boxes = []; 123 | if (_gradientConfig != null && _gradientConfig!.ignoreRegex != null) { 124 | _gradientConfig!.ignoreRegex!.allMatches(_textPainter.plainText).forEach( 125 | (RegExpMatch match) { 126 | final int start = match.start; 127 | final int end = match.end; 128 | final TextSelection textSelection = 129 | TextSelection(baseOffset: start, extentOffset: end); 130 | boxes.addAll(_textPainter.getBoxesForSelection(textSelection)); 131 | }, 132 | ); 133 | } 134 | 135 | if (_textPainter.text != null) { 136 | void _findIgnoreGradientSpan(InlineSpan span, int startIndex) { 137 | if (span is IgnoreGradientSpan) { 138 | final int length = span.toPlainText().length; 139 | final TextSelection textSelection = TextSelection( 140 | baseOffset: startIndex, extentOffset: startIndex + length); 141 | boxes.addAll(_textPainter.getBoxesForSelection(textSelection)); 142 | // IgnoreGradientSpan and it's children should not be applied to the gradient. 143 | return; 144 | } 145 | 146 | if (span is TextSpan && span.children != null) { 147 | int childStartIndex = startIndex; 148 | for (final InlineSpan child in span.children!) { 149 | _findIgnoreGradientSpan(child, childStartIndex); 150 | childStartIndex += child.toPlainText().length; 151 | } 152 | } 153 | } 154 | 155 | _findIgnoreGradientSpan(_textPainter.text!, 0); 156 | } 157 | 158 | _ignoreGradientWithBoxes(boxes, context, offset); 159 | 160 | return boxes.isNotEmpty; 161 | } 162 | 163 | void _ignoreGradientWithBoxes( 164 | List boxes, PaintingContext context, ui.Offset offset) { 165 | if (boxes.isNotEmpty) { 166 | for (final ui.TextBox box in boxes) { 167 | final Rect rect = box.toRect(); 168 | if (!rect.isEmpty) { 169 | context.canvas.clipRect( 170 | rect.shift(offset), 171 | clipOp: ui.ClipOp.difference, 172 | ); 173 | } 174 | } 175 | } 176 | } 177 | 178 | /// Helper method to actually draw the gradient on the specified rectangle. 179 | void _drawGradientWithRect(ui.Rect rect, PaintingContext context) { 180 | if (rect.isEmpty || _gradientConfig == null) { 181 | return; 182 | } 183 | final ui.Shader shader = _gradientConfig!.gradient.createShader(rect); 184 | final ui.Paint paint = Paint() 185 | ..shader = shader 186 | ..blendMode = _gradientConfig!.blendMode; 187 | 188 | // Draw the gradient within the rectangle. 189 | context.canvas.drawRect(rect, paint); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /lib/src/extended/selection_mixin.dart: -------------------------------------------------------------------------------- 1 | part of 'package:extended_text/src/extended/rendering/paragraph.dart'; 2 | 3 | mixin SelectionMixin on TextOverflowMixin { 4 | bool _canSelectPlaceholderSpan = true; 5 | bool get canSelectPlaceholderSpan => _canSelectPlaceholderSpan; 6 | set canSelectPlaceholderSpan(bool value) { 7 | if (_canSelectPlaceholderSpan != value) { 8 | _canSelectPlaceholderSpan = value; 9 | } 10 | } 11 | 12 | @override 13 | List<_SelectableFragment> _getSelectableFragments() { 14 | final List<_SelectableFragment> result = <_SelectableFragment>[]; 15 | int start = 0; 16 | final String plainText = text.toPlainText(includeSemanticsLabels: false); 17 | 18 | text.visitChildren((InlineSpan span) { 19 | final int length = ExtendedTextLibraryUtils.getInlineOffset(span); 20 | 21 | if (length == 0) { 22 | return true; 23 | } else if (span is PlaceholderSpan && !canSelectPlaceholderSpan) { 24 | start += length; 25 | return true; 26 | } else { 27 | // overflow widget should not be select 28 | if (_overflowSelections != null) { 29 | for (final _TextRange _overflowSelection in _overflowSelections!) { 30 | final List range = 31 | List.generate(length, (int index) => start + index); 32 | for (int i = _overflowSelection.start; 33 | i < _overflowSelection.end; 34 | i++) { 35 | range.remove(i); 36 | } 37 | 38 | if (range.isEmpty) { 39 | start += length; 40 | return true; 41 | } 42 | final List temp = [ 43 | range[0], 44 | ]; 45 | 46 | void _add() { 47 | result.add(_ExtendedSelectableFragment( 48 | paragraph: this, 49 | range: 50 | TextRange(start: temp.first, end: temp.first + temp.length), 51 | fullText: plainText, 52 | specialInlineSpanBase: span is SpecialInlineSpanBase 53 | ? span as SpecialInlineSpanBase 54 | : null, 55 | )); 56 | temp.clear(); 57 | } 58 | 59 | for (int i = 1; i < range.length; i++) { 60 | if (temp.last + 1 != range[i]) { 61 | _add(); 62 | } 63 | temp.add(range[i]); 64 | } 65 | 66 | if (temp.isNotEmpty) { 67 | _add(); 68 | } 69 | } 70 | } else { 71 | result.add( 72 | _ExtendedSelectableFragment( 73 | paragraph: this, 74 | range: TextRange(start: start, end: start + length), 75 | fullText: plainText, 76 | specialInlineSpanBase: span is SpecialInlineSpanBase 77 | ? span as SpecialInlineSpanBase 78 | : null, 79 | ), 80 | ); 81 | } 82 | } 83 | start += length; 84 | 85 | return true; 86 | }); 87 | return result; 88 | } 89 | } 90 | 91 | class _ExtendedSelectableFragment extends _SelectableFragment { 92 | _ExtendedSelectableFragment({ 93 | required super.paragraph, 94 | required super.fullText, 95 | required super.range, 96 | this.specialInlineSpanBase, 97 | }); 98 | 99 | final SpecialInlineSpanBase? specialInlineSpanBase; 100 | 101 | bool get _deleteAll => specialInlineSpanBase?.deleteAll ?? false; 102 | 103 | @override 104 | SelectedContent? getSelectedContent() { 105 | if (_textSelectionStart == null || _textSelectionEnd == null) { 106 | return null; 107 | } 108 | 109 | if (specialInlineSpanBase != null) { 110 | final int start = 111 | math.min(_textSelectionStart!.offset, _textSelectionEnd!.offset); 112 | final int end = 113 | math.max(_textSelectionStart!.offset, _textSelectionEnd!.offset); 114 | 115 | if (start == end) { 116 | return null; 117 | } 118 | if (range.start <= start && end <= range.end) { 119 | return SelectedContent( 120 | plainText: 121 | specialInlineSpanBase!.getSelectedContent(fullText.substring( 122 | start, 123 | end, 124 | ))); 125 | } else { 126 | return null; 127 | } 128 | } 129 | return super.getSelectedContent(); 130 | } 131 | 132 | @override 133 | void _setSelectionPosition(TextPosition? position, {required bool isEnd}) { 134 | if (_deleteAll && position != null) { 135 | // zmtzawqlp 136 | // move 137 | if (range.start < position.offset && position.offset < range.end) { 138 | final double half = (range.end - range.start) / 2; 139 | if (position.offset < range.start + half) { 140 | position = 141 | TextPosition(offset: range.start, affinity: position.affinity); 142 | } else { 143 | position = 144 | TextPosition(offset: range.end, affinity: position.affinity); 145 | } 146 | } 147 | } 148 | 149 | super._setSelectionPosition(position, isEnd: isEnd); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/extended/widgets/rich_text.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' as ui; 2 | import 'package:extended_text/src/extended/gradient/gradient_config.dart'; 3 | import 'package:extended_text/src/extended/rendering/paragraph.dart'; 4 | import 'package:extended_text/src/extended/widgets/text_overflow_widget.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/rendering.dart'; 7 | 8 | part 'package:extended_text/src/official/widgets/rich_text.dart'; 9 | 10 | class ExtendedRichText extends _RichText { 11 | ExtendedRichText({ 12 | super.key, 13 | required super.text, 14 | super.textAlign = TextAlign.start, 15 | super.textDirection, 16 | super.softWrap = true, 17 | super.overflow = TextOverflow.clip, 18 | super.textScaler = TextScaler.noScaling, 19 | super.maxLines, 20 | super.locale, 21 | super.strutStyle, 22 | super.textWidthBasis = TextWidthBasis.parent, 23 | super.textHeightBehavior, 24 | super.selectionRegistrar, 25 | super.selectionColor, 26 | this.overflowWidget, 27 | this.canSelectPlaceholderSpan = true, 28 | this.gradientConfig, 29 | }) : super( 30 | children: _extractChildren(text, overflowWidget, textScaler), 31 | ); 32 | 33 | final TextOverflowWidget? overflowWidget; 34 | 35 | /// if false, it will skip PlaceholderSpan 36 | final bool canSelectPlaceholderSpan; 37 | 38 | /// Configuration for applying gradients to text. 39 | /// 40 | /// [gradient] is the gradient that will be applied to the text. 41 | /// [ignoreWidgetSpan] determines whether `WidgetSpan` elements should be 42 | /// included in the gradient application. By default, widget spans are ignored. 43 | /// [mode] specifies how the gradient should be applied to the text. The default 44 | /// is [GradientRenderMode.fullText], meaning the gradient will apply to the entire text. 45 | /// [ignoreRegex] is a regular expression used to exclude certain parts of the text 46 | /// from the gradient effect. For example, it can be used to exclude specific characters 47 | /// or words (like emojis or special symbols) from the gradient application. 48 | final GradientConfig? gradientConfig; 49 | @override 50 | ExtendedRenderParagraph createRenderObject(BuildContext context) { 51 | assert(textDirection != null || debugCheckHasDirectionality(context)); 52 | return ExtendedRenderParagraph( 53 | text, 54 | textAlign: textAlign, 55 | textDirection: textDirection ?? Directionality.of(context), 56 | softWrap: softWrap, 57 | overflow: overflow, 58 | textScaler: textScaler, 59 | maxLines: maxLines, 60 | strutStyle: strutStyle, 61 | textWidthBasis: textWidthBasis, 62 | textHeightBehavior: textHeightBehavior, 63 | locale: locale ?? Localizations.maybeLocaleOf(context), 64 | registrar: selectionRegistrar, 65 | selectionColor: selectionColor, 66 | overflowWidget: overflowWidget, 67 | canSelectPlaceholderSpan: canSelectPlaceholderSpan, 68 | gradientConfig: gradientConfig, 69 | ); 70 | } 71 | 72 | @override 73 | void updateRenderObject( 74 | BuildContext context, ExtendedRenderParagraph renderObject) { 75 | assert(textDirection != null || debugCheckHasDirectionality(context)); 76 | renderObject 77 | ..text = text 78 | ..textAlign = textAlign 79 | ..textDirection = textDirection ?? Directionality.of(context) 80 | ..softWrap = softWrap 81 | ..overflow = overflow 82 | ..textScaler = textScaler 83 | ..maxLines = maxLines 84 | ..strutStyle = strutStyle 85 | ..textWidthBasis = textWidthBasis 86 | ..textHeightBehavior = textHeightBehavior 87 | ..locale = locale ?? Localizations.maybeLocaleOf(context) 88 | ..registrar = selectionRegistrar 89 | ..selectionColor = selectionColor 90 | ..overflowWidget = overflowWidget 91 | ..canSelectPlaceholderSpan = canSelectPlaceholderSpan 92 | ..gradientConfig = gradientConfig; 93 | } 94 | 95 | /// Traverses the InlineSpan tree and depth-first collects the list of 96 | /// child widgets that are created in WidgetSpans. 97 | // TODO(zmtzawqlp): _extractChildren has replace with WidgetSpan.extractFromInlineSpan 98 | static List _extractChildren( 99 | InlineSpan span, 100 | TextOverflowWidget? overflowWidget, 101 | TextScaler textScaler, 102 | ) { 103 | final List result = [ 104 | ...WidgetSpan.extractFromInlineSpan(span, textScaler) 105 | ]; 106 | 107 | if (overflowWidget != null) { 108 | result.add(Semantics( 109 | tagForChildren: PlaceholderSpanIndexSemanticsTag(result.length), 110 | child: overflowWidget, 111 | )); 112 | if (overflowWidget.position == TextOverflowPosition.auto) { 113 | result.add(Semantics( 114 | tagForChildren: PlaceholderSpanIndexSemanticsTag(result.length), 115 | child: overflowWidget, 116 | )); 117 | } 118 | } 119 | return result; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/extended/widgets/text_overflow_widget.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: zmtzawqlp 3 | * @Date: 2020-06-25 01:29:01 4 | * @Last Modified by: zmtzawqlp 5 | * @Last Modified time: 2020-06-25 02:13:14 6 | */ 7 | import 'package:flutter/widgets.dart'; 8 | 9 | enum TextOverflowAlign { 10 | /// Align the [TextOverflowWidget] on the left edge of the Text Overflow Rect. 11 | left, 12 | 13 | /// Align the [TextOverflowWidget] on the right edge of the Text Overflow Rect. 14 | right, 15 | 16 | /// Align the [TextOverflowWidget] on the center of the Text Overflow Rect. 17 | center, 18 | } 19 | 20 | /// The position which TextOverflowWidget should be shown 21 | /// https://github.com/flutter/flutter/issues/45336 22 | enum TextOverflowPosition { 23 | start, 24 | middle, 25 | end, 26 | 27 | /// The position of TextOverflowWidget is decided by the position of TextOverflowWidget. 28 | auto, 29 | } 30 | 31 | /// https://github.com/fluttercandies/extended_text/issues/118 32 | /// Clear the text under TextOverflowWidget 33 | /// default: Paint()..BlendMode.clear 34 | /// Canvas.clipRect 35 | /// BlendMode.clear will make BackdropFilter to be black background 36 | enum TextOverflowClearType { 37 | /// 38 | clipRect, 39 | 40 | /// Paint()..BlendMode.clear 41 | blendModeClear, 42 | } 43 | 44 | class TextOverflowWidget extends StatelessWidget { 45 | const TextOverflowWidget({ 46 | required this.child, 47 | this.align = TextOverflowAlign.right, 48 | this.maxHeight, 49 | this.position = TextOverflowPosition.end, 50 | this.debugOverflowRectColor, 51 | this.clearType = TextOverflowClearType.clipRect, 52 | }); 53 | 54 | /// The widget of TextOverflow. 55 | final Widget child; 56 | 57 | /// The Align of [TextOverflowWidget]. 58 | final TextOverflowAlign align; 59 | 60 | /// The maxHeight of [TextOverflowWidget], default is preferredLineHeight. 61 | final double? maxHeight; 62 | 63 | /// The position which TextOverflowWidget should be shown 64 | final TextOverflowPosition position; 65 | 66 | /// Whether paint overflow rect, just for debug 67 | /// https://github.com/flutter/flutter/issues/45336 68 | final Color? debugOverflowRectColor; 69 | 70 | /// https://github.com/fluttercandies/extended_text/issues/118 71 | /// Clear the text under TextOverflowWidget 72 | /// default: Paint()..BlendMode.clear 73 | /// Canvas.clipRect 74 | /// BlendMode.clear will make BackdropFilter to be black background 75 | final TextOverflowClearType clearType; 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return child; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: extended_text 2 | description: Extended official text to build special text like inline image or @somebody quickly,it also support custom background,custom over flow,gradient and custom selection toolbar and handles. 3 | version: 15.0.2 4 | repository: https://github.com/fluttercandies/extended_text 5 | issue_tracker: https://github.com/fluttercandies/extended_text/issues 6 | topics: 7 | - extended-text 8 | - custom-text-overflow 9 | - gradient-text 10 | 11 | environment: 12 | sdk: '>=3.7.0 <4.0.0' 13 | flutter: ">=3.29.0" 14 | 15 | dependencies: 16 | extended_text_library: ^12.0.1 17 | # version: ^11.0.0-dev.1 18 | # hosted: "https://pub.dev" 19 | flutter: 20 | sdk: flutter 21 | # dependency_overrides: 22 | # extended_text_library: 23 | # path: ../extended_text_library 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/main_test.dart: -------------------------------------------------------------------------------- 1 | // import 'package:extended_text/extended_text.dart'; 2 | // import 'package:flutter/material.dart'; 3 | // import 'package:flutter_test/flutter_test.dart'; 4 | 5 | // void main() { 6 | // testWidgets( 7 | // 'widget should display go to bottom button when the bottom of the page is not visible', 8 | // (WidgetTester tester) async { 9 | // final Widget w = _buildWidget(); 10 | // await tester.pumpWidget(w); 11 | // }); 12 | // } 13 | 14 | // Widget _buildWidget() { 15 | // return MaterialApp( 16 | // home: Scaffold( 17 | // body: ConstrainedBox( 18 | // constraints: const BoxConstraints(maxHeight: 100, maxWidth: 100), 19 | // child: Container( 20 | // width: 50, 21 | // height: 50, 22 | // child: ExtendedText.rich( 23 | // _buildText(), 24 | // maxLines: 5, 25 | // overflow: TextOverflow.clip, 26 | // overflowWidget: TextOverflowWidget( 27 | // align: TextOverflowAlign.left, 28 | // child: Container( 29 | // child: const Text('overflow'), 30 | // height: 100, 31 | // width: 100, 32 | // ), 33 | // ), 34 | // )), 35 | // ))); 36 | // } 37 | 38 | // TextSpan _buildText() { 39 | // return const TextSpan(text: 'text'); 40 | // } 41 | --------------------------------------------------------------------------------