├── doc
├── entryPoint.md
├── customization.md
├── models.md
└── defaultWidgets.md
├── DashChat.png
├── example
├── ios
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── 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-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── .gitignore
│ ├── Podfile
│ └── Podfile.lock
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ └── Icon-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── 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
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── examples
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── .metadata
├── README.md
├── lib
│ ├── samples
│ │ ├── media.dart
│ │ ├── avatar.dart
│ │ ├── typing_users_sample.dart
│ │ ├── basic.dart
│ │ ├── send_on_enter.dart
│ │ ├── quick_replies_sample.dart
│ │ ├── theming.dart
│ │ └── mention.dart
│ ├── main.dart
│ └── data.dart
├── .gitignore
├── analysis_options.yaml
└── pubspec.yaml
├── assets
├── placeholder.png
└── profile_placeholder.png
├── test
└── dash_chat_test.dart
├── lib
├── src
│ ├── widgets
│ │ ├── image_provider
│ │ │ ├── image_provider.dart
│ │ │ ├── image_provider_web.dart
│ │ │ └── image_provider_mobile.dart
│ │ ├── input_toolbar
│ │ │ ├── default_send_button.dart
│ │ │ ├── default_input_decoration.dart
│ │ │ └── input_toolbar.dart
│ │ ├── message_row
│ │ │ ├── default_message_decoration.dart
│ │ │ ├── default_user_name.dart
│ │ │ ├── default_parse_patterns.dart
│ │ │ ├── video_player.dart
│ │ │ ├── text_container.dart
│ │ │ ├── default_avatar.dart
│ │ │ ├── default_message_text.dart
│ │ │ ├── media_container.dart
│ │ │ └── message_row.dart
│ │ ├── typing_users
│ │ │ ├── default_typing_builder.dart
│ │ │ └── typing_indicator.dart
│ │ ├── quick_replies
│ │ │ ├── quick_replies.dart
│ │ │ └── default_quick_reply.dart
│ │ └── message_list
│ │ │ ├── default_date_separator.dart
│ │ │ ├── default_scroll_to_bottom.dart
│ │ │ └── message_list.dart
│ ├── helpers
│ │ └── link_helper.dart
│ ├── models
│ │ ├── cursor_style.dart
│ │ ├── scroll_to_bottom_options.dart
│ │ ├── mention.dart
│ │ ├── quick_reply_options.dart
│ │ ├── quick_reply.dart
│ │ ├── chat_user.dart
│ │ ├── message_list_options.dart
│ │ ├── chat_media.dart
│ │ ├── input_options.dart
│ │ ├── chat_message.dart
│ │ └── message_options.dart
│ └── dash_chat.dart
└── dash_chat_2.dart
├── .metadata
├── dartdoc_options.yaml
├── LICENSE
├── .gitignore
├── pubspec.yaml
├── CHANGELOG.md
├── analysis_options.yaml
├── .all-contributorsrc
├── README.md
└── pubspec.lock
/doc/entryPoint.md:
--------------------------------------------------------------------------------
1 | # Entry Point
2 |
3 | The overall widget of the lib
--------------------------------------------------------------------------------
/DashChat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/DashChat.png
--------------------------------------------------------------------------------
/doc/customization.md:
--------------------------------------------------------------------------------
1 | # Customizations
2 |
3 | Options to customize the UI of the chat
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/doc/models.md:
--------------------------------------------------------------------------------
1 | # Models
2 |
3 | Data structures to pass your messages, users... to the lib
--------------------------------------------------------------------------------
/assets/placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/assets/placeholder.png
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/web/favicon.png
--------------------------------------------------------------------------------
/assets/profile_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/assets/profile_placeholder.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/test/dash_chat_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | void main() {
4 | test('', () {});
5 | }
6 |
--------------------------------------------------------------------------------
/lib/src/widgets/image_provider/image_provider.dart:
--------------------------------------------------------------------------------
1 | export 'image_provider_mobile.dart'
2 | if (dart.library.html) 'image_provider_web.dart';
3 |
--------------------------------------------------------------------------------
/doc/defaultWidgets.md:
--------------------------------------------------------------------------------
1 | # Default widgets
2 |
3 | Widgets you can use if you only want to override few params but don't want to rewrite the entire widget
--------------------------------------------------------------------------------
/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/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/lib/src/helpers/link_helper.dart:
--------------------------------------------------------------------------------
1 | import 'package:url_launcher/url_launcher.dart';
2 |
3 | void openLink(String link) {
4 | launchUrl(Uri.parse(link), mode: LaunchMode.externalApplication);
5 | }
6 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/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/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SebastienBtr/Dash-Chat-2/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/examples/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.examples
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/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 and should not be manually edited.
5 |
6 | version:
7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/lib/src/widgets/image_provider/image_provider_web.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ImageProvider getImageProvider(String url) {
4 | if (url.startsWith('http')) {
5 | return NetworkImage(url);
6 | } else {
7 | return const AssetImage(
8 | 'assets/placeholder.png',
9 | package: 'dash_chat_2',
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/dartdoc_options.yaml:
--------------------------------------------------------------------------------
1 | dartdoc:
2 | categories:
3 | "Entry point":
4 | markdown: doc/entryPoint.md
5 | "Models":
6 | markdown: doc/models.md
7 | "Customization":
8 | markdown: doc/customization.md
9 | "Default widgets":
10 | markdown: doc/defaultWidgets.md
11 | categoryOrder: ["Entry point", "Models", "Customization", "Default widgets"]
12 | showUndocumentedCategories: true
13 |
--------------------------------------------------------------------------------
/lib/src/models/cursor_style.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class CursorStyle {
5 | const CursorStyle({
6 | this.color,
7 | this.hide = false,
8 | this.width = 2.0,
9 | });
10 |
11 | /// Color of the cursor
12 | final Color? color;
13 |
14 | /// Hide or not the cursor
15 | final bool hide;
16 |
17 | /// Width of the cursor
18 | final double width;
19 | }
20 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
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 |
--------------------------------------------------------------------------------
/lib/src/widgets/image_provider/image_provider_mobile.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:cached_network_image/cached_network_image.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | ImageProvider getImageProvider(String url) {
7 | if (url.startsWith('http')) {
8 | return CachedNetworkImageProvider(url);
9 | } else if (url.startsWith('assets')) {
10 | return AssetImage(url);
11 | } else {
12 | return FileImage(
13 | File(url),
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/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/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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/README.md:
--------------------------------------------------------------------------------
1 | # examples
2 |
3 | A new Flutter project.
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.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/lib/src/widgets/input_toolbar/default_send_button.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | Widget Function(Function send) defaultSendButton({
5 | required Color color,
6 | IconData icon = Icons.send,
7 | EdgeInsets? padding,
8 | bool disabled = false,
9 | }) =>
10 | (Function fct) => InkWell(
11 | onTap: disabled ? null : () => fct(),
12 | child: Padding(
13 | padding: padding ??
14 | const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
15 | child: Icon(
16 | icon,
17 | color: color,
18 | ),
19 | ),
20 | );
21 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples",
3 | "short_name": "examples",
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 | }
24 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/default_message_decoration.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | BoxDecoration defaultMessageDecoration({
5 | required Color color,
6 | required double borderTopLeft,
7 | required double borderTopRight,
8 | required double borderBottomLeft,
9 | required double borderBottomRight,
10 | }) =>
11 | BoxDecoration(
12 | color: color,
13 | borderRadius: BorderRadius.only(
14 | topLeft: Radius.circular(borderTopLeft),
15 | topRight: Radius.circular(borderTopRight),
16 | bottomLeft: Radius.circular(borderBottomLeft),
17 | bottomRight: Radius.circular(borderBottomRight),
18 | ),
19 | );
20 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.9.0'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.0.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | tasks.register("clean", Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/ephemeral/
22 | Flutter/app.flx
23 | Flutter/app.zip
24 | Flutter/flutter_assets/
25 | Flutter/flutter_export_environment.sh
26 | ServiceDefinitions.json
27 | Runner/GeneratedPluginRegistrant.*
28 |
29 | # Exceptions to above rules.
30 | !default.mode1v3
31 | !default.mode2v3
32 | !default.pbxuser
33 | !default.perspectivev3
34 |
--------------------------------------------------------------------------------
/lib/src/models/scroll_to_bottom_options.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class ScrollToBottomOptions {
5 | const ScrollToBottomOptions({
6 | this.disabled = false,
7 | this.scrollToBottomBuilder,
8 | this.onScrollToBottomPress,
9 | });
10 |
11 | /// If you don't want to show the scroll-to-bottom widget
12 | final bool disabled;
13 |
14 | /// Builder to create your own scroll-to-bottom widget
15 | /// You can use DefaultScrollToBottom to only override some variables
16 | final Widget Function(ScrollController scrollController)?
17 | scrollToBottomBuilder;
18 |
19 | /// Function to call when the scroll-to-bottom widget is pressed
20 | /// It will scroll down in any case
21 | final void Function()? onScrollToBottomPress;
22 | }
23 |
--------------------------------------------------------------------------------
/example/lib/samples/media.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class Media extends StatefulWidget {
6 | @override
7 | State createState() => MediaState();
8 | }
9 |
10 | class MediaState extends State {
11 | List messages = media;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('Media example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | messages: messages,
27 | ),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/lib/samples/avatar.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class AvatarSample extends StatefulWidget {
6 | @override
7 | State createState() => AvatarSampleState();
8 | }
9 |
10 | class AvatarSampleState extends State {
11 | List messages = allUsersSample;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('Users example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | messages: messages,
27 | ),
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/src/widgets/input_toolbar/default_input_decoration.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | InputDecoration defaultInputDecoration({
5 | String hintText = 'Write a message...',
6 | TextStyle hintStyle = const TextStyle(color: Colors.grey),
7 | Color? fillColor,
8 | }) =>
9 | InputDecoration(
10 | isDense: true,
11 | hintText: hintText,
12 | hintStyle: hintStyle,
13 | filled: true,
14 | fillColor: fillColor ?? Colors.grey[100],
15 | contentPadding: const EdgeInsets.only(
16 | left: 18,
17 | top: 10,
18 | bottom: 10,
19 | ),
20 | border: OutlineInputBorder(
21 | borderRadius: BorderRadius.circular(25),
22 | borderSide: const BorderSide(
23 | width: 0,
24 | style: BorderStyle.none,
25 | ),
26 | ),
27 | );
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/default_user_name.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultUserName extends StatelessWidget {
5 | const DefaultUserName({
6 | required this.user,
7 | this.style,
8 | this.padding,
9 | Key? key,
10 | }) : super(key: key);
11 |
12 | /// User to show
13 | final ChatUser user;
14 |
15 | /// Style of the text
16 | final TextStyle? style;
17 |
18 | /// Padding around the text
19 | final EdgeInsets? padding;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Padding(
24 | padding: padding ?? const EdgeInsets.symmetric(horizontal: 10),
25 | child: Text(
26 | user.getFullName(),
27 | style: style ??
28 | const TextStyle(
29 | fontSize: 10,
30 | color: Colors.grey,
31 | ),
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/lib/samples/typing_users_sample.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class TypingUsersSample extends StatefulWidget {
6 | @override
7 | State createState() => TypingUsersSampleState();
8 | }
9 |
10 | class TypingUsersSampleState extends State {
11 | List messages = basicSample;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('Typing user example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | typingUsers: [user3],
27 | messages: messages,
28 | ),
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
--------------------------------------------------------------------------------
/lib/src/models/mention.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Models}
4 | class Mention {
5 | Mention({
6 | required this.title,
7 | this.customProperties,
8 | });
9 |
10 | /// Create a Mention instance from json data
11 | factory Mention.fromJson(Map jsonData) {
12 | return Mention(
13 | title: jsonData['title'].toString(),
14 | customProperties: jsonData['customProperties'] as Map?,
15 | );
16 | }
17 |
18 | /// Title of the mention,
19 | /// it's what is visible in the message: @userName
20 | String title;
21 |
22 | /// A list of custom properties to save any data you might need
23 | /// For instance a user Id
24 | Map? customProperties;
25 |
26 | /// Convert a Mention into a json
27 | Map toJson() {
28 | return {
29 | 'title': title,
30 | 'customProperties': customProperties,
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/example/lib/samples/basic.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class Basic extends StatefulWidget {
6 | @override
7 | State createState() => BasicState();
8 | }
9 |
10 | class BasicState extends State {
11 | List messages = basicSample;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('Basic example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | messages: messages,
27 | messageListOptions: MessageListOptions(
28 | onLoadEarlier: () async {
29 | await Future.delayed(const Duration(seconds: 3));
30 | },
31 | ),
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/src/models/quick_reply_options.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class QuickReplyOptions {
5 | const QuickReplyOptions({
6 | this.onTapQuickReply,
7 | this.quickReplyPadding,
8 | this.quickReplyMargin,
9 | this.quickReplyStyle,
10 | this.quickReplyTextStyle,
11 | this.quickReplyBuilder,
12 | });
13 |
14 | /// Function to call when the user click on a quick reply
15 | /// Use that to create a message and send it
16 | final Function(QuickReply)? onTapQuickReply;
17 |
18 | /// Padding of a quick reply container
19 | final EdgeInsets? quickReplyPadding;
20 |
21 | /// Margin of a quick reply container
22 | final EdgeInsets? quickReplyMargin;
23 |
24 | /// BoxDecoration of a quick reply container
25 | final BoxDecoration? quickReplyStyle;
26 |
27 | /// TextStyle of a quick reply
28 | final TextStyle? quickReplyTextStyle;
29 |
30 | /// Builder to create your own quickReply builder
31 | final Widget Function(QuickReply)? quickReplyBuilder;
32 | }
33 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Molteo
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.
22 |
--------------------------------------------------------------------------------
/example/lib/samples/send_on_enter.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class SendOnEnter extends StatefulWidget {
6 | @override
7 | State createState() => SendOnEnterState();
8 | }
9 |
10 | class SendOnEnterState extends State {
11 | List messages = basicSample;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('SendOnEnter example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | messages: messages,
27 | inputOptions: const InputOptions(
28 | sendOnEnter: true,
29 | ),
30 | messageListOptions: MessageListOptions(
31 | onLoadEarlier: () async {
32 | await Future.delayed(const Duration(seconds: 3));
33 | },
34 | ),
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/default_parse_patterns.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | @Deprecated('Use defaultParsePatterns instead')
4 | List defaultPersePatterns = defaultParsePatterns;
5 |
6 | /// {@category Default widgets}
7 | List defaultParsePatterns = [
8 | MatchText(
9 | type: ParsedType.EMAIL,
10 | style: const TextStyle(
11 | decoration: TextDecoration.underline,
12 | ),
13 | onTap: (String email) {
14 | String url = 'mailto:$email';
15 | openLink(url);
16 | },
17 | ),
18 | MatchText(
19 | type: ParsedType.URL,
20 | style: const TextStyle(
21 | decoration: TextDecoration.underline,
22 | ),
23 | onTap: (String url) {
24 | if (!url.startsWith('http://') && !url.startsWith('https://')) {
25 | url = 'http://$url';
26 | }
27 | openLink(url);
28 | },
29 | ),
30 | MatchText(
31 | pattern: r'(\+|00|0)?[1-9][0-9 \-\(\)\.]{5,32}[0-9](?!\w)',
32 | type: ParsedType.CUSTOM,
33 | style: const TextStyle(
34 | decoration: TextDecoration.underline,
35 | ),
36 | onTap: (String phone) {
37 | String url = 'tel:$phone';
38 | openLink(url);
39 | },
40 | ),
41 | ];
42 |
--------------------------------------------------------------------------------
/lib/src/widgets/typing_users/default_typing_builder.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultTypingBuilder extends StatelessWidget {
5 | const DefaultTypingBuilder({
6 | required this.user,
7 | this.text = 'is typing',
8 | Key? key,
9 | }) : super(key: key);
10 |
11 | /// User that is typing
12 | final ChatUser user;
13 |
14 | /// Text to show after user's name in the indicator
15 | final String text;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Padding(
20 | padding: const EdgeInsets.only(left: 15, top: 25),
21 | child: Row(
22 | crossAxisAlignment: CrossAxisAlignment.end,
23 | children: [
24 | const Padding(
25 | padding: EdgeInsets.only(right: 2),
26 | child: TypingIndicator(),
27 | ),
28 | Text(
29 | user.getFullName(),
30 | style: const TextStyle(
31 | fontSize: 12,
32 | fontWeight: FontWeight.bold,
33 | ),
34 | ),
35 | Text(
36 | ' $text',
37 | style: const TextStyle(fontSize: 12),
38 | ),
39 | ],
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/lib/samples/quick_replies_sample.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class QuickRepliesSample extends StatefulWidget {
6 | @override
7 | State createState() => QuickRepliesSampleState();
8 | }
9 |
10 | class QuickRepliesSampleState extends State {
11 | List messages = quickReplies;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('QuickReplies example'),
18 | ),
19 | body: DashChat(
20 | currentUser: user,
21 | onSend: (ChatMessage m) {
22 | setState(() {
23 | messages.insert(0, m);
24 | });
25 | },
26 | quickReplyOptions: QuickReplyOptions(onTapQuickReply: (QuickReply r) {
27 | final ChatMessage m = ChatMessage(
28 | user: user,
29 | text: r.value ?? r.title,
30 | createdAt: DateTime.now(),
31 | );
32 | setState(() {
33 | messages.insert(0, m);
34 | });
35 | }),
36 | messages: messages,
37 | ),
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/src/models/quick_reply.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Models}
4 | class QuickReply {
5 | QuickReply({
6 | required this.title,
7 | this.value,
8 | this.customProperties,
9 | });
10 |
11 | /// Create a QuickReply instance from json data
12 | factory QuickReply.fromJson(Map jsonData) {
13 | return QuickReply(
14 | title: jsonData['title'].toString(),
15 | value: jsonData['value']?.toString(),
16 | customProperties: jsonData['customProperties'] as Map?,
17 | );
18 | }
19 |
20 | /// Title of the quick reply,
21 | /// it's what will be visible in the quick replies list
22 | String title;
23 |
24 | /// Actual value of the quick reply
25 | /// Use that if you want to have a message text different from the title
26 | String? value;
27 |
28 | /// A list of custom properties to extend the existing ones
29 | /// in case you need to store more things.
30 | /// Can be useful to extend existing features
31 | Map? customProperties;
32 |
33 | /// Convert a QuickReply into a json
34 | Map toJson() {
35 | return {
36 | 'title': title,
37 | 'value': value,
38 | 'customProperties': customProperties,
39 | };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/src/widgets/quick_replies/quick_replies.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class QuickReplies extends StatelessWidget {
5 | const QuickReplies({
6 | required this.quickReplies,
7 | this.quickReplyOptions = const QuickReplyOptions(),
8 | Key? key,
9 | }) : super(key: key);
10 |
11 | /// List of quick replies to show
12 | final List quickReplies;
13 |
14 | /// Options used to customize quick replies behaviour and design
15 | final QuickReplyOptions quickReplyOptions;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Column(
20 | mainAxisSize: MainAxisSize.min,
21 | crossAxisAlignment: CrossAxisAlignment.start,
22 | children: [
23 | SingleChildScrollView(
24 | scrollDirection: Axis.horizontal,
25 | child: Padding(
26 | padding: const EdgeInsets.all(8.0),
27 | child: Row(
28 | children: quickReplies.map((QuickReply r) {
29 | return quickReplyOptions.quickReplyBuilder != null
30 | ? quickReplyOptions.quickReplyBuilder!(r)
31 | : DefaultQuickReply(
32 | quickReply: r,
33 | quickReplyOptions: quickReplyOptions,
34 | );
35 | }).toList(),
36 | ),
37 | ),
38 | ),
39 | ],
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | use_key_in_widget_constructors: false
26 | prefer_single_quotes: true
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - path_provider_foundation (0.0.1):
4 | - Flutter
5 | - FlutterMacOS
6 | - sqflite (0.0.3):
7 | - Flutter
8 | - FlutterMacOS
9 | - url_launcher_ios (0.0.1):
10 | - Flutter
11 | - video_player_avfoundation (0.0.1):
12 | - Flutter
13 | - FlutterMacOS
14 |
15 | DEPENDENCIES:
16 | - Flutter (from `Flutter`)
17 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
18 | - sqflite (from `.symlinks/plugins/sqflite/darwin`)
19 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
20 | - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
21 |
22 | EXTERNAL SOURCES:
23 | Flutter:
24 | :path: Flutter
25 | path_provider_foundation:
26 | :path: ".symlinks/plugins/path_provider_foundation/darwin"
27 | sqflite:
28 | :path: ".symlinks/plugins/sqflite/darwin"
29 | url_launcher_ios:
30 | :path: ".symlinks/plugins/url_launcher_ios/ios"
31 | video_player_avfoundation:
32 | :path: ".symlinks/plugins/video_player_avfoundation/darwin"
33 |
34 | SPEC CHECKSUMS:
35 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
36 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
37 | sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
38 | url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
39 | video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
40 |
41 | PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011
42 |
43 | COCOAPODS: 1.13.0
44 |
--------------------------------------------------------------------------------
/lib/src/widgets/quick_replies/default_quick_reply.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultQuickReply extends StatelessWidget {
5 | const DefaultQuickReply({
6 | required this.quickReply,
7 | this.quickReplyOptions = const QuickReplyOptions(),
8 | Key? key,
9 | }) : super(key: key);
10 |
11 | /// Options used to customize quick replies behaviour and design
12 | final QuickReplyOptions quickReplyOptions;
13 |
14 | /// Quick reply to show
15 | final QuickReply quickReply;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return GestureDetector(
20 | onTap: quickReplyOptions.onTapQuickReply != null
21 | ? () => quickReplyOptions.onTapQuickReply!(quickReply)
22 | : null,
23 | child: Container(
24 | margin: quickReplyOptions.quickReplyMargin ??
25 | const EdgeInsets.symmetric(horizontal: 5),
26 | padding: quickReplyOptions.quickReplyPadding ??
27 | const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
28 | decoration: quickReplyOptions.quickReplyStyle ??
29 | BoxDecoration(
30 | border: Border.all(color: Theme.of(context).primaryColor),
31 | borderRadius: const BorderRadius.all(Radius.circular(5.0)),
32 | ),
33 | child: Text(
34 | quickReply.title,
35 | style: quickReplyOptions.quickReplyTextStyle ??
36 | TextStyle(color: Theme.of(context).primaryColor),
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Flutter.podspec
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/ephemeral
64 | **/ios/Flutter/app.flx
65 | **/ios/Flutter/app.zip
66 | **/ios/Flutter/flutter_assets/
67 | **/ios/Flutter/flutter_export_environment.sh
68 | **/ios/ServiceDefinitions.json
69 | **/ios/Runner/GeneratedPluginRegistrant.*
70 |
71 | # Exceptions to above rules.
72 | !**/ios/**/default.mode1v3
73 | !**/ios/**/default.mode2v3
74 | !**/ios/**/default.pbxuser
75 | !**/ios/**/default.perspectivev3
76 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | examples
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/lib/src/models/chat_user.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Models}
4 | class ChatUser {
5 | ChatUser({
6 | required this.id,
7 | this.profileImage,
8 | this.customProperties,
9 | this.firstName,
10 | this.lastName,
11 | });
12 |
13 | /// Create a ChatUser instance from json data
14 | factory ChatUser.fromJson(Map jsonData) {
15 | return ChatUser(
16 | id: jsonData['id'].toString(),
17 | profileImage: jsonData['profileImage']?.toString(),
18 | firstName: jsonData['firstName']?.toString(),
19 | lastName: jsonData['lastName']?.toString(),
20 | customProperties: jsonData['customProperties'] as Map?,
21 | );
22 | }
23 |
24 | /// Id of the user
25 | String id;
26 |
27 | /// Profile image of the user
28 | String? profileImage;
29 |
30 | /// A list of custom properties to extend the existing ones
31 | /// in case you need to store more things.
32 | /// Can be useful to extend existing features
33 | Map? customProperties;
34 |
35 | /// First name of the user,
36 | /// if you only have the name as one string
37 | /// you can put the entire value in the [firstName] field
38 | String? firstName;
39 |
40 | /// Last name of the user
41 | String? lastName;
42 |
43 | /// Get the full name (firstName + lastName) of the user
44 | String getFullName() {
45 | return (firstName ?? '') +
46 | (firstName != null && lastName != null
47 | ? ' ${lastName!}'
48 | : lastName ?? '');
49 | }
50 |
51 | /// Convert a ChatUser into a json
52 | Map toJson() {
53 | return {
54 | 'id': id,
55 | 'profileImage': profileImage,
56 | 'firstName': firstName,
57 | 'lastName': lastName,
58 | 'customProperties': customProperties,
59 | };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_list/default_date_separator.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultDateSeparator extends StatelessWidget {
5 | const DefaultDateSeparator({
6 | required this.date,
7 | this.messageListOptions = const MessageListOptions(),
8 | this.padding = const EdgeInsets.symmetric(vertical: 20),
9 | this.textStyle = const TextStyle(color: Colors.grey),
10 | Key? key,
11 | }) : super(key: key);
12 |
13 | /// Date to show
14 | final DateTime date;
15 |
16 | /// Options to customize the behaviour and design of the overall list of message
17 | final MessageListOptions messageListOptions;
18 |
19 | /// Padding of the separator
20 | final EdgeInsets padding;
21 |
22 | /// Style of the text
23 | final TextStyle textStyle;
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return Padding(
28 | padding: padding,
29 | child: Text(
30 | _formatDateSeparator(date),
31 | style: textStyle,
32 | ),
33 | );
34 | }
35 |
36 | String _formatDateSeparator(DateTime date) {
37 | if (messageListOptions.dateSeparatorFormat != null) {
38 | return messageListOptions.dateSeparatorFormat!.format(date);
39 | }
40 |
41 | final DateTime today = DateTime.now();
42 |
43 | if (date.year != today.year) {
44 | return intl.DateFormat('dd MMM yyyy, HH:mm').format(date);
45 | } else if (date.month != today.month ||
46 | _getWeekOfYear(date) != _getWeekOfYear(today)) {
47 | return intl.DateFormat('dd MMM HH:mm').format(date);
48 | } else if (date.day != today.day) {
49 | return intl.DateFormat('E HH:mm').format(date);
50 | }
51 | return intl.DateFormat('HH:mm').format(date);
52 | }
53 |
54 | int _getWeekOfYear(DateTime date) {
55 | final int dayOfYear = int.parse(intl.DateFormat('D').format(date));
56 | return ((dayOfYear - date.weekday + 10) / 7).floor();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: dash_chat_2
2 | description: The most complete Chat UI for flutter. Easy to use, highly customizable and fully featured
3 | version: 0.0.21
4 | homepage: https://github.com/SebastienBtr/Dash-Chat-2
5 |
6 | environment:
7 | sdk: ">=2.12.0 <4.0.0"
8 | flutter: ">=1.17.0"
9 |
10 | dependencies:
11 | cached_network_image: ^3.3.1
12 | flutter:
13 | sdk: flutter
14 | flutter_markdown: ^0.7.1
15 | flutter_parsed_text: ^2.2.1
16 | intl: ^0.19.0
17 | url_launcher: ^6.0.10
18 | video_player: ^2.2.5
19 |
20 | dev_dependencies:
21 | flutter_lints: ^3.0.1
22 | flutter_test:
23 | sdk: flutter
24 |
25 | # For information on the generic Dart part of this file, see the
26 | # following page: https://dart.dev/tools/pub/pubspec
27 |
28 | # The following section is specific to Flutter.
29 | flutter:
30 |
31 | # To add assets to your package, add an assets section, like this:
32 | assets:
33 | - assets/
34 | #
35 | # For details regarding assets in packages, see
36 | # https://flutter.dev/assets-and-images/#from-packages
37 | #
38 | # An image asset can refer to one or more resolution-specific "variants", see
39 | # https://flutter.dev/assets-and-images/#resolution-aware.
40 |
41 | # To add custom fonts to your package, add a fonts section here,
42 | # in this "flutter" section. Each entry in this list should have a
43 | # "family" key with the font family name, and a "fonts" key with a
44 | # list giving the asset and other descriptors for the font. For
45 | # example:
46 | # fonts:
47 | # - family: Schyler
48 | # fonts:
49 | # - asset: fonts/Schyler-Regular.ttf
50 | # - asset: fonts/Schyler-Italic.ttf
51 | # style: italic
52 | # - family: Trajan Pro
53 | # fonts:
54 | # - asset: fonts/TrajanPro.ttf
55 | # - asset: fonts/TrajanPro_Bold.ttf
56 | # weight: 700
57 | #
58 | # For details regarding fonts in packages, see
59 | # https://flutter.dev/custom-fonts/#from-packages
60 |
--------------------------------------------------------------------------------
/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 | compileSdkVersion 33
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | defaultConfig {
36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
37 | applicationId "com.example.examples"
38 | minSdkVersion flutter.minSdkVersion
39 | targetSdkVersion 30
40 | versionCode flutterVersionCode.toInteger()
41 | versionName flutterVersionName
42 | multiDexEnabled true
43 | }
44 |
45 | buildTypes {
46 | release {
47 | // TODO: Add your own signing config for the release build.
48 | // Signing with the debug keys for now, so `flutter run --release` works.
49 | signingConfig signingConfigs.debug
50 | }
51 | }
52 | }
53 |
54 | flutter {
55 | source '../..'
56 | }
57 |
58 | dependencies {
59 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
60 | implementation 'androidx.multidex:multidex:2.0.1'
61 | }
62 |
--------------------------------------------------------------------------------
/lib/src/models/message_list_options.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class MessageListOptions {
5 | const MessageListOptions({
6 | this.showDateSeparator = true,
7 | this.dateSeparatorFormat,
8 | this.dateSeparatorBuilder,
9 | this.separatorFrequency = SeparatorFrequency.days,
10 | this.scrollController,
11 | this.chatFooterBuilder,
12 | this.showFooterBeforeQuickReplies = false,
13 | this.loadEarlierBuilder,
14 | this.onLoadEarlier,
15 | this.typingBuilder,
16 | this.scrollPhysics,
17 | });
18 |
19 | /// If you want to who a date separator between messages of different dates
20 | final bool showDateSeparator;
21 |
22 | /// The formatting of the date in the date separator.
23 | /// By default it will adapt according to the difference with today
24 | final intl.DateFormat? dateSeparatorFormat;
25 |
26 | /// If you want to create you own separator widget
27 | /// You can use DefaultDateSeparator to only override some variables
28 | final Widget Function(DateTime date)? dateSeparatorBuilder;
29 |
30 | /// The frequency of the separator
31 | final SeparatorFrequency separatorFrequency;
32 |
33 | /// Scroll controller of the list of message
34 | final ScrollController? scrollController;
35 |
36 | /// A widget to show at the bottom of the chat
37 | /// (between the input and the chat content)
38 | final Widget? chatFooterBuilder;
39 |
40 | /// If you want to show [chatFooterBuilder] before or after the quick replies
41 | final bool showFooterBeforeQuickReplies;
42 |
43 | /// If you want to show a widget when the top of the list is reached
44 | final Widget? loadEarlierBuilder;
45 |
46 | /// Function to call when the top of the list is reached
47 | /// Useful to load more messages
48 | final Future Function()? onLoadEarlier;
49 |
50 | /// Builder to create your own typing widget
51 | final Widget Function(ChatUser user)? typingBuilder;
52 |
53 | /// Scroll physics of the ListView
54 | final ScrollPhysics? scrollPhysics;
55 | }
56 |
57 | enum SeparatorFrequency { days, hours }
58 |
--------------------------------------------------------------------------------
/lib/dash_chat_2.dart:
--------------------------------------------------------------------------------
1 | library dash_chat_2;
2 |
3 | import 'dart:math';
4 |
5 | import 'package:flutter/gestures.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_markdown/flutter_markdown.dart';
8 | import 'package:flutter_parsed_text/flutter_parsed_text.dart';
9 | import 'package:intl/intl.dart' as intl;
10 | import 'package:video_player/video_player.dart' as vp;
11 |
12 | import 'src/helpers/link_helper.dart';
13 | import 'src/widgets/image_provider/image_provider.dart';
14 |
15 | export 'package:flutter_parsed_text/flutter_parsed_text.dart';
16 |
17 | part 'src/dash_chat.dart';
18 | part 'src/models/chat_media.dart';
19 | part 'src/models/chat_message.dart';
20 | part 'src/models/chat_user.dart';
21 | part 'src/models/cursor_style.dart';
22 | part 'src/models/input_options.dart';
23 | part 'src/models/mention.dart';
24 | part 'src/models/message_list_options.dart';
25 | part 'src/models/message_options.dart';
26 | part 'src/models/quick_reply.dart';
27 | part 'src/models/quick_reply_options.dart';
28 | part 'src/models/scroll_to_bottom_options.dart';
29 | part 'src/widgets/input_toolbar/default_input_decoration.dart';
30 | part 'src/widgets/input_toolbar/default_send_button.dart';
31 | part 'src/widgets/input_toolbar/input_toolbar.dart';
32 | part 'src/widgets/message_list/default_date_separator.dart';
33 | part 'src/widgets/message_list/default_scroll_to_bottom.dart';
34 | part 'src/widgets/message_list/message_list.dart';
35 | part 'src/widgets/message_row/default_avatar.dart';
36 | part 'src/widgets/message_row/default_message_decoration.dart';
37 | part 'src/widgets/message_row/default_message_text.dart';
38 | part 'src/widgets/message_row/default_parse_patterns.dart';
39 | part 'src/widgets/message_row/default_user_name.dart';
40 | part 'src/widgets/message_row/media_container.dart';
41 | part 'src/widgets/message_row/message_row.dart';
42 | part 'src/widgets/message_row/text_container.dart';
43 | part 'src/widgets/message_row/video_player.dart';
44 | part 'src/widgets/quick_replies/default_quick_reply.dart';
45 | part 'src/widgets/quick_replies/quick_replies.dart';
46 | part 'src/widgets/typing_users/default_typing_builder.dart';
47 | part 'src/widgets/typing_users/typing_indicator.dart';
48 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
13 |
17 |
21 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lib/src/widgets/typing_users/typing_indicator.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class TypingIndicator extends StatefulWidget {
5 | const TypingIndicator({
6 | Key? key,
7 | this.flashingCircleDarkColor = const Color(0xFF333333),
8 | this.flashingCircleBrightColor = const Color(0xFFaec1dd),
9 | }) : super(key: key);
10 |
11 | /// Dark color in the animation
12 | final Color flashingCircleDarkColor;
13 |
14 | /// Light color in the animation
15 | final Color flashingCircleBrightColor;
16 |
17 | @override
18 | State createState() => TypingIndicatorState();
19 | }
20 |
21 | class TypingIndicatorState extends State
22 | with TickerProviderStateMixin {
23 | late AnimationController _repeatingController;
24 | final List _dotIntervals = const [
25 | Interval(0.25, 0.8),
26 | Interval(0.35, 0.9),
27 | Interval(0.45, 1.0),
28 | ];
29 |
30 | @override
31 | void initState() {
32 | super.initState();
33 | _repeatingController = AnimationController(
34 | vsync: this,
35 | duration: const Duration(milliseconds: 1200),
36 | );
37 | _repeatingController.repeat();
38 | }
39 |
40 | @override
41 | void dispose() {
42 | _repeatingController.dispose();
43 | super.dispose();
44 | }
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | return SizedBox(
49 | width: 30,
50 | height: 15,
51 | child: Row(
52 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
53 | children: [
54 | _buildFlashingCircle(0),
55 | _buildFlashingCircle(1),
56 | _buildFlashingCircle(2),
57 | ],
58 | ),
59 | );
60 | }
61 |
62 | Widget _buildFlashingCircle(int index) {
63 | return AnimatedBuilder(
64 | animation: _repeatingController,
65 | builder: (BuildContext context, Widget? child) {
66 | final double circleFlashPercent =
67 | _dotIntervals[index].transform(_repeatingController.value);
68 | final double circleColorPercent = sin(pi * circleFlashPercent);
69 |
70 | return Container(
71 | width: 5,
72 | height: 5,
73 | decoration: BoxDecoration(
74 | shape: BoxShape.circle,
75 | color: Color.lerp(widget.flashingCircleDarkColor,
76 | widget.flashingCircleBrightColor, circleColorPercent),
77 | ),
78 | );
79 | },
80 | );
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.0.21] - 02/06/2024
2 |
3 | * Upgraded dependencies
4 | * Added support for markdown stylesheet
5 | * Fixed defaultSendButton not using the icon param
6 |
7 | ## [0.0.20] - 01/02/2024
8 |
9 | * Markdown support
10 | * Fixed issue with mentions, see [#67](https://github.com/SebastienBtr/Dash-Chat-2/issues/67)
11 | * Fixed issue with videos, see [#70](https://github.com/SebastienBtr/Dash-Chat-2/issues/70)
12 | * Overall maintenance and minor code improvements
13 |
14 | ## [0.0.19] - 23/10/2023
15 |
16 | * More parse pattern by default
17 | * Fixed typo for defaultParsePatterns variable
18 |
19 | ## [0.0.18] - 22/08/2023
20 |
21 | * Allowed customProperties to be null
22 | * Upgrade dependencies
23 |
24 | ## [0.0.17] - 16/05/2023
25 |
26 | * Made it compatible with Flutter 3.10
27 | * Added more customisation on MessageOption and improved code quality
28 |
29 | ## [0.0.16] - 10/05/2023
30 |
31 | * Upgraded dependencies
32 | * Removed top safe area padding for the input widget
33 | * Added possibility to add asset image as user icon
34 | * Added black background to videos
35 |
36 | ## [0.0.15] - 03/10/2022
37 |
38 | * Fixed opening dynamic links (deeplinks)
39 | * Fixed issue with video previews
40 | * Fixed issues related to the mention overlay
41 |
42 | ## [0.0.14] - 23/08/2022
43 |
44 | * Hid the mention overlay when the input toolbar is disposed
45 |
46 | ## [0.0.13] - 22/08/2022
47 |
48 | * Added ability to mention anything using any trigger (for instance: @username)
49 | * Added maxWidth message option
50 |
51 | ## [0.0.12] - 19/07/2022
52 |
53 | * Added new param to the video player
54 |
55 | ## [0.0.11] - 06/07/2022
56 |
57 | * Improved default send button padding
58 |
59 | ## [0.0.10] - 30/06/2022
60 |
61 | * Improved tap gesture area of default send button
62 |
63 | ## [0.0.9] - 29/06/2022
64 |
65 | * Improved tap gesture area of default send button
66 |
67 | ## [0.0.8] - 28/06/2022
68 |
69 | * Fixed `sendOnEnter` param not working
70 |
71 | ## [0.0.7] - 27/06/2022
72 |
73 | * Upgraded dependencies
74 | * Fixed example
75 | * Added `scrollPhysics` param in `MessageListOptions`
76 |
77 | ## [0.0.6] - 21/04/2022
78 |
79 | * Added flutter web support
80 |
81 | ## [0.0.4] - 03/03/2022
82 |
83 | * Fixed scrollController issue for onLoadEarlier
84 | * UI improvements
85 |
86 | ## [0.0.3] - 27/09/2021
87 |
88 | * Change better_player to video_player to have support for web
89 |
90 | ## [0.0.2] - 24/09/2021
91 |
92 | * Fix readme image issue
93 | * Add description in pubspec
94 |
95 | ## [0.0.1] - 24/09/2021
96 |
97 | * First version
98 |
--------------------------------------------------------------------------------
/lib/src/dash_chat.dart:
--------------------------------------------------------------------------------
1 | part of '../dash_chat_2.dart';
2 |
3 | /// {@category Entry point}
4 | class DashChat extends StatelessWidget {
5 | const DashChat({
6 | required this.currentUser,
7 | required this.onSend,
8 | required this.messages,
9 | this.inputOptions = const InputOptions(),
10 | this.messageOptions = const MessageOptions(),
11 | this.messageListOptions = const MessageListOptions(),
12 | this.quickReplyOptions = const QuickReplyOptions(),
13 | this.scrollToBottomOptions = const ScrollToBottomOptions(),
14 | this.readOnly = false,
15 | this.typingUsers,
16 | Key? key,
17 | }) : super(key: key);
18 |
19 | /// The current user of the chat
20 | final ChatUser currentUser;
21 |
22 | /// Function to call when the user sends a message
23 | final void Function(ChatMessage message) onSend;
24 |
25 | /// List of messages visible in the chat
26 | final List messages;
27 |
28 | /// Options to customize the behaviour and design of the chat input
29 | final InputOptions inputOptions;
30 |
31 | /// Options to customize the behaviour and design of the messages
32 | final MessageOptions messageOptions;
33 |
34 | /// Options to customize the behaviour and design of the overall list of message
35 | final MessageListOptions messageListOptions;
36 |
37 | /// Options to customize the behaviour and design of the quick replies
38 | final QuickReplyOptions quickReplyOptions;
39 |
40 | /// Options to customize the behaviour and design of the scroll-to-bottom button
41 | final ScrollToBottomOptions scrollToBottomOptions;
42 |
43 | /// Option to make the chat read only, it will hide the input field
44 | final bool readOnly;
45 |
46 | /// List of users currently typing in the chat
47 | final List? typingUsers;
48 |
49 | @override
50 | Widget build(BuildContext context) {
51 | return Column(
52 | children: [
53 | Expanded(
54 | child: MessageList(
55 | currentUser: currentUser,
56 | messages: messages,
57 | messageOptions: messageOptions,
58 | messageListOptions: messageListOptions,
59 | quickReplyOptions: quickReplyOptions,
60 | scrollToBottomOptions: scrollToBottomOptions,
61 | typingUsers: typingUsers,
62 | readOnly: readOnly,
63 | ),
64 | ),
65 | if (!readOnly)
66 | InputToolbar(
67 | inputOptions: inputOptions,
68 | currentUser: currentUser,
69 | onSend: onSend,
70 | ),
71 | ],
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/src/models/chat_media.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Models}
4 | class ChatMedia {
5 | ChatMedia({
6 | required this.url,
7 | required this.fileName,
8 | required this.type,
9 | this.isUploading = false,
10 | this.uploadedDate,
11 | this.customProperties,
12 | });
13 |
14 | /// Create a ChatMedia instance from json data
15 | factory ChatMedia.fromJson(Map jsonData) {
16 | return ChatMedia(
17 | url: jsonData['url'].toString(),
18 | fileName: jsonData['fileName'].toString(),
19 | type: MediaType.parse(jsonData['type'].toString()),
20 | isUploading: jsonData['isUploading'] == true,
21 | uploadedDate: jsonData['uploadedDate'] != null
22 | ? DateTime.parse(jsonData['uploadedDate'].toString()).toLocal()
23 | : null,
24 | customProperties: jsonData['customProperties'] as Map?,
25 | );
26 | }
27 |
28 | /// URL of the media, can local (will use FileImage) or remote (will use NetworkImage)
29 | String url;
30 |
31 | /// Name of the file that will be shown in some cases
32 | String fileName;
33 |
34 | /// Type of media
35 | MediaType type;
36 |
37 | /// If the media is still uploading, useful to add a visual feedback
38 | bool isUploading;
39 |
40 | /// Uploaded date of the media
41 | DateTime? uploadedDate;
42 |
43 | /// A list of custom properties to extend the existing ones
44 | /// in case you need to store more things.
45 | /// Can be useful to extend existing features
46 | Map? customProperties;
47 |
48 | /// Convert a ChatMedia into a json
49 | Map toJson() {
50 | return {
51 | 'url': url,
52 | 'type': type.toString(),
53 | 'fileName': fileName,
54 | 'isUploading': isUploading,
55 | 'uploadedDate': uploadedDate?.toUtc().toIso8601String(),
56 | 'customProperties': customProperties,
57 | };
58 | }
59 | }
60 |
61 | class MediaType {
62 | const MediaType._internal(this._value);
63 | final String _value;
64 |
65 | @override
66 | String toString() => _value;
67 |
68 | static MediaType parse(String value) {
69 | switch (value) {
70 | case 'image':
71 | return MediaType.image;
72 | case 'video':
73 | return MediaType.video;
74 | case 'file':
75 | return MediaType.file;
76 | default:
77 | throw UnsupportedError('$value is not a valid MediaType');
78 | }
79 | }
80 |
81 | static const MediaType image = MediaType._internal('image');
82 | static const MediaType video = MediaType._internal('video');
83 | static const MediaType file = MediaType._internal('file');
84 | }
85 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/video_player.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class VideoPlayer extends StatefulWidget {
5 | const VideoPlayer({
6 | required this.url,
7 | this.aspectRatio = 1,
8 | this.canPlay = true,
9 | Key? key,
10 | }) : super(key: key);
11 |
12 | /// Link of the video
13 | final String url;
14 |
15 | /// The Aspect Ratio of the Video. Important to get the correct size of the video
16 | final double aspectRatio;
17 |
18 | /// If the video can be played
19 | final bool canPlay;
20 |
21 | @override
22 | State createState() => VideoPlayerState();
23 | }
24 |
25 | class VideoPlayerState extends State {
26 | late vp.VideoPlayerController _controller;
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 | _controller = vp.VideoPlayerController.networkUrl(Uri.parse(widget.url))
32 | ..initialize().then((_) {
33 | // Ensure the first frame is shown after the video is initialized,
34 | // even before the play button has been pressed.
35 | setState(() {});
36 | });
37 | }
38 |
39 | @override
40 | void dispose() {
41 | super.dispose();
42 | _controller.dispose();
43 | }
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return _controller.value.isInitialized
48 | ? Container(
49 | color: Colors.black,
50 | child: Stack(
51 | alignment: _controller.value.isPlaying
52 | ? AlignmentDirectional.bottomStart
53 | : AlignmentDirectional.center,
54 | children: [
55 | AspectRatio(
56 | aspectRatio: _controller.value.aspectRatio,
57 | child: vp.VideoPlayer(_controller),
58 | ),
59 | IconButton(
60 | iconSize: _controller.value.isPlaying ? 24 : 60,
61 | onPressed: widget.canPlay
62 | ? () {
63 | setState(() {
64 | _controller.value.isPlaying
65 | ? _controller.pause()
66 | : _controller.play();
67 | });
68 | }
69 | : null,
70 | icon: Icon(
71 | _controller.value.isPlaying
72 | ? Icons.pause
73 | : Icons.play_arrow,
74 | color: Colors.white,
75 | // size: 60,
76 | ),
77 | ),
78 | ],
79 | ),
80 | )
81 | : Container(color: Colors.black);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:examples/samples/avatar.dart';
2 | import 'package:examples/samples/basic.dart';
3 | import 'package:examples/samples/media.dart';
4 | import 'package:examples/samples/mention.dart';
5 | import 'package:examples/samples/quick_replies_sample.dart';
6 | import 'package:examples/samples/send_on_enter.dart';
7 | import 'package:examples/samples/theming.dart';
8 | import 'package:examples/samples/typing_users_sample.dart';
9 | import 'package:flutter/material.dart';
10 |
11 | void main() {
12 | runApp(MyApp());
13 | }
14 |
15 | class MyApp extends StatelessWidget {
16 | // This widget is the root of your application.
17 | @override
18 | Widget build(BuildContext context) {
19 | return MaterialApp(
20 | title: 'Dash Chat Demo',
21 | theme: ThemeData(
22 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.tealAccent),
23 | ),
24 | home: MyHomePage(),
25 | );
26 | }
27 | }
28 |
29 | class MyHomePage extends StatefulWidget {
30 | @override
31 | State createState() => MyHomePageState();
32 | }
33 |
34 | class MyHomePageState extends State {
35 | @override
36 | Widget build(BuildContext context) {
37 | return Scaffold(
38 | appBar: AppBar(
39 | title: const Text('Dash Chat Demo'),
40 | ),
41 | body: Center(
42 | child: Column(
43 | mainAxisAlignment: MainAxisAlignment.center,
44 | children: [
45 | ElevatedButton(
46 | onPressed: () => push(Basic()),
47 | child: const Text('Basic'),
48 | ),
49 | ElevatedButton(
50 | onPressed: () => push(Media()),
51 | child: const Text('Chat media'),
52 | ),
53 | ElevatedButton(
54 | onPressed: () => push(AvatarSample()),
55 | child: const Text('All user possibilities'),
56 | ),
57 | ElevatedButton(
58 | onPressed: () => push(QuickRepliesSample()),
59 | child: const Text('Quick replies'),
60 | ),
61 | ElevatedButton(
62 | onPressed: () => push(TypingUsersSample()),
63 | child: const Text('Typing users'),
64 | ),
65 | ElevatedButton(
66 | onPressed: () => push(SendOnEnter()),
67 | child: const Text('Send on enter'),
68 | ),
69 | ElevatedButton(
70 | onPressed: () => push(MentionSample()),
71 | child: const Text('Mention'),
72 | ),
73 | ElevatedButton(
74 | onPressed: () => push(ThemeSample()),
75 | child: const Text('Theming'),
76 | ),
77 | ],
78 | ),
79 | ),
80 | );
81 | }
82 |
83 | void push(Widget page) {
84 | Navigator.of(context).push(
85 | MaterialPageRoute(
86 | builder: (BuildContext context) {
87 | return page;
88 | },
89 | ),
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: examples
2 | description: A new Flutter project.
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.12.0 <3.0.0"
22 |
23 | dependencies:
24 | dash_chat_2:
25 | path: ../
26 | flutter:
27 | sdk: flutter
28 |
29 | dev_dependencies:
30 | flutter_lints: ^2.0.1
31 | flutter_test:
32 | sdk: flutter
33 |
34 | # For information on the generic Dart part of this file, see the
35 | # following page: https://dart.dev/tools/pub/pubspec
36 |
37 | # The following section is specific to Flutter.
38 | flutter:
39 |
40 | # The following line ensures that the Material Icons font is
41 | # included with your application, so that you can use the icons in
42 | # the material Icons class.
43 | uses-material-design: true
44 |
45 | # To add assets to your application, add an assets section, like this:
46 | # assets:
47 | # - images/a_dot_burr.jpeg
48 | # - images/a_dot_ham.jpeg
49 |
50 | # An image asset can refer to one or more resolution-specific "variants", see
51 | # https://flutter.dev/assets-and-images/#resolution-aware.
52 |
53 | # For details regarding adding assets from package dependencies, see
54 | # https://flutter.dev/assets-and-images/#from-packages
55 |
56 | # To add custom fonts to your application, add a fonts section here,
57 | # in this "flutter" section. Each entry in this list should have a
58 | # "family" key with the font family name, and a "fonts" key with a
59 | # list giving the asset and other descriptors for the font. For
60 | # example:
61 | # fonts:
62 | # - family: Schyler
63 | # fonts:
64 | # - asset: fonts/Schyler-Regular.ttf
65 | # - asset: fonts/Schyler-Italic.ttf
66 | # style: italic
67 | # - family: Trajan Pro
68 | # fonts:
69 | # - asset: fonts/TrajanPro.ttf
70 | # - asset: fonts/TrajanPro_Bold.ttf
71 | # weight: 700
72 | #
73 | # For details regarding fonts from package dependencies,
74 | # see https://flutter.dev/custom-fonts/#from-packages
75 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | analyzer:
13 | language:
14 | strict-casts: true
15 | strict-raw-types: true
16 |
17 | linter:
18 | # The lint rules applied to this project can be customized in the
19 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
20 | # included above or to enable additional rules. A list of all available lints
21 | # and their documentation is published at
22 | # https://dart-lang.github.io/linter/lints/index.html.
23 | #
24 | # Instead of disabling a lint rule for the entire project in the
25 | # section below, it can also be suppressed for a single line of code
26 | # or a specific dart file by using the `// ignore: name_of_lint` and
27 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
28 | # producing the lint.
29 | rules:
30 | always_declare_return_types: true
31 | always_put_control_body_on_new_line: true
32 | always_put_required_named_parameters_first: true
33 | always_specify_types: true
34 | avoid_bool_literals_in_conditional_expressions: true
35 | avoid_empty_else: true
36 | avoid_slow_async_io: true
37 | avoid_types_as_parameter_names: true
38 | avoid_unused_constructor_parameters: true
39 | avoid_void_async: true
40 | await_only_futures: true
41 | camel_case_extensions: true
42 | camel_case_types: true
43 | cancel_subscriptions: true
44 | collection_methods_unrelated_type: true
45 | directives_ordering: true
46 | empty_catches: true
47 | no_duplicate_case_values: true
48 | non_constant_identifier_names: true
49 | package_api_docs: true
50 | prefer_final_in_for_each: true
51 | prefer_if_elements_to_conditional_expressions: true
52 | prefer_is_empty: true
53 | prefer_is_not_empty: true
54 | prefer_relative_imports: true
55 | prefer_single_quotes: true
56 | prefer_typing_uninitialized_variables: true
57 | sort_constructors_first: true
58 | sort_pub_dependencies: true
59 | sort_unnamed_constructors_first: true
60 | test_types_in_equals: true
61 | throw_in_finally: true
62 | unnecessary_null_aware_assignments: true
63 | unnecessary_overrides: true
64 | unnecessary_parenthesis: true
65 | unnecessary_statements: true
66 | unrelated_type_equality_checks: true
67 | valid_regexps: true
68 | void_checks: true
69 |
70 | use_key_in_widget_constructors: false
71 | constant_identifier_names: false
72 | file_names: false
73 |
74 | # Additional information about this file can be found at
75 | # https://dart.dev/guides/language/analysis-options
76 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_list/default_scroll_to_bottom.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultScrollToBottom extends StatelessWidget {
5 | const DefaultScrollToBottom({
6 | required this.scrollController,
7 | this.readOnly = false,
8 | this.backgroundColor,
9 | this.textColor,
10 | this.bottom = 10.0,
11 | this.left = 0.0,
12 | this.right = 0.0,
13 | this.top,
14 | this.height = 30.0,
15 | this.width = 30.0,
16 | this.elevation = 5,
17 | this.icon = Icons.arrow_downward,
18 | this.iconSize = 18,
19 | this.onScrollToBottomPress,
20 | Key? key,
21 | }) : super(key: key);
22 |
23 | /// Scroll controller of the chat list
24 | final ScrollController scrollController;
25 |
26 | /// Background color of the button
27 | final Color? backgroundColor;
28 |
29 | /// Icon color of the button
30 | final Color? textColor;
31 |
32 | /// The distance that the child's bottom edge is inset from the bottom of the stack
33 | final double? bottom;
34 |
35 | /// The distance that the child's left edge is inset from the left of the stack
36 | final double? left;
37 |
38 | /// The distance that the child's right edge is inset from the right of the stack
39 | final double? right;
40 |
41 | /// The distance that the child's top edge is inset from the top of the stack
42 | final double? top;
43 |
44 | /// Height of the button
45 | final double height;
46 |
47 | /// Width of the button
48 | final double width;
49 |
50 | /// Elevation of the button
51 | final double elevation;
52 |
53 | /// Icon of the button
54 | final IconData icon;
55 |
56 | /// Icon size
57 | final double iconSize;
58 |
59 | /// Function to call when the scroll-to-bottom widget is pressed
60 | /// It will scroll down in any case
61 | final void Function()? onScrollToBottomPress;
62 |
63 | /// Whether the chat is read only, used to add safe area padding to bottom
64 | final bool readOnly;
65 |
66 | @override
67 | Widget build(BuildContext context) {
68 | return Positioned(
69 | right: right,
70 | left: left,
71 | top: top,
72 | bottom: readOnly
73 | ? MediaQuery.of(context).padding.bottom + (bottom ?? 0)
74 | : bottom,
75 | child: SizedBox(
76 | width: width,
77 | height: height,
78 | child: RawMaterialButton(
79 | elevation: elevation,
80 | fillColor: backgroundColor,
81 | shape: const CircleBorder(),
82 | child: Icon(
83 | icon,
84 | size: iconSize,
85 | color: textColor,
86 | ),
87 | onPressed: () {
88 | if (onScrollToBottomPress != null) {
89 | onScrollToBottomPress!();
90 | }
91 | scrollController.animateTo(
92 | 0.0,
93 | duration: const Duration(milliseconds: 300),
94 | curve: Curves.easeInOut,
95 | );
96 | },
97 | ),
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "SebastienBtr",
10 | "name": "SebastienBtr",
11 | "avatar_url": "https://avatars.githubusercontent.com/u/18089010?v=4",
12 | "profile": "https://github.com/SebastienBtr",
13 | "contributions": [
14 | "code",
15 | "design"
16 | ]
17 | },
18 | {
19 | "login": "chuusungmin",
20 | "name": "chuusungmin",
21 | "avatar_url": "https://avatars.githubusercontent.com/u/17997403?v=4",
22 | "profile": "https://github.com/chuusungmin",
23 | "contributions": [
24 | "code"
25 | ]
26 | },
27 | {
28 | "login": "fufesou",
29 | "name": "fufesou",
30 | "avatar_url": "https://avatars.githubusercontent.com/u/13586388?v=4",
31 | "profile": "https://github.com/fufesou",
32 | "contributions": [
33 | "code"
34 | ]
35 | },
36 | {
37 | "login": "farmery",
38 | "name": "Nwachi ifeanyichukwu Victor",
39 | "avatar_url": "https://avatars.githubusercontent.com/u/56759256?v=4",
40 | "profile": "https://github.com/farmery",
41 | "contributions": [
42 | "code"
43 | ]
44 | },
45 | {
46 | "login": "kaedeee",
47 | "name": "Kaede Games",
48 | "avatar_url": "https://avatars.githubusercontent.com/u/55743370?v=4",
49 | "profile": "https://github.com/kaedeee",
50 | "contributions": [
51 | "code"
52 | ]
53 | },
54 | {
55 | "login": "derekpitts28",
56 | "name": "Derek Pitts",
57 | "avatar_url": "https://avatars.githubusercontent.com/u/83979577?v=4",
58 | "profile": "https://github.com/derekpitts28",
59 | "contributions": [
60 | "code"
61 | ]
62 | },
63 | {
64 | "login": "LegendAF",
65 | "name": "Alex Fernandez",
66 | "avatar_url": "https://avatars.githubusercontent.com/u/825344?v=4",
67 | "profile": "https://github.com/LegendAF",
68 | "contributions": [
69 | "code"
70 | ]
71 | },
72 | {
73 | "login": "funjay",
74 | "name": "lawrence",
75 | "avatar_url": "https://avatars.githubusercontent.com/u/66911451?v=4",
76 | "profile": "https://github.com/funjay",
77 | "contributions": [
78 | "code"
79 | ]
80 | },
81 | {
82 | "login": "alamin-karno",
83 | "name": "Md. Al-Amin",
84 | "avatar_url": "https://avatars.githubusercontent.com/u/56608168?v=4",
85 | "profile": "https://github.com/alamin-karno",
86 | "contributions": [
87 | "code"
88 | ]
89 | },
90 | {
91 | "login": "T-P-F",
92 | "name": "TPF",
93 | "avatar_url": "https://avatars.githubusercontent.com/u/61667947?v=4",
94 | "profile": "https://github.com/T-P-F",
95 | "contributions": [
96 | "code"
97 | ]
98 | }
99 | ],
100 | "contributorsPerLine": 7,
101 | "projectName": "Dash-Chat-2",
102 | "projectOwner": "SebastienBtr",
103 | "repoType": "github",
104 | "repoHost": "https://github.com",
105 | "skipCi": true,
106 | "commitConvention": "angular",
107 | "commitType": "docs"
108 | }
109 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/text_container.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class TextContainer extends StatelessWidget {
5 | const TextContainer({
6 | required this.message,
7 | this.messageOptions = const MessageOptions(),
8 | this.previousMessage,
9 | this.nextMessage,
10 | this.isOwnMessage = false,
11 | this.isPreviousSameAuthor = false,
12 | this.isNextSameAuthor = false,
13 | this.isAfterDateSeparator = false,
14 | this.isBeforeDateSeparator = false,
15 | this.messageTextBuilder,
16 | Key? key,
17 | }) : super(key: key);
18 |
19 | /// Options to customize the behaviour and design of the messages
20 | final MessageOptions messageOptions;
21 |
22 | /// Message that contains the text to show
23 | final ChatMessage message;
24 |
25 | /// Previous message in the list
26 | final ChatMessage? previousMessage;
27 |
28 | /// Next message in the list
29 | final ChatMessage? nextMessage;
30 |
31 | /// If the message is from the current user
32 | final bool isOwnMessage;
33 |
34 | /// If the previous message is from the same author as the current one
35 | final bool isPreviousSameAuthor;
36 |
37 | /// If the next message is from the same author as the current one
38 | final bool isNextSameAuthor;
39 |
40 | /// If the message is preceded by a date separator
41 | final bool isAfterDateSeparator;
42 |
43 | /// If the message is before by a date separator
44 | final bool isBeforeDateSeparator;
45 |
46 | /// We could access that from messageOptions but we want to reuse this widget
47 | /// for media and be able to override the text builder
48 | final Widget Function(ChatMessage, ChatMessage?, ChatMessage?)?
49 | messageTextBuilder;
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return Container(
54 | decoration: messageOptions.messageDecorationBuilder != null
55 | ? messageOptions.messageDecorationBuilder!(
56 | message, previousMessage, nextMessage)
57 | : defaultMessageDecoration(
58 | color: isOwnMessage
59 | ? messageOptions.currentUserContainerColor(context)
60 | : messageOptions.containerColor,
61 | borderTopLeft:
62 | isPreviousSameAuthor && !isOwnMessage && !isAfterDateSeparator
63 | ? 0.0
64 | : messageOptions.borderRadius,
65 | borderTopRight:
66 | isPreviousSameAuthor && isOwnMessage && !isAfterDateSeparator
67 | ? 0.0
68 | : messageOptions.borderRadius,
69 | borderBottomLeft:
70 | !isOwnMessage && !isBeforeDateSeparator && isNextSameAuthor
71 | ? 0.0
72 | : messageOptions.borderRadius,
73 | borderBottomRight:
74 | isOwnMessage && !isBeforeDateSeparator && isNextSameAuthor
75 | ? 0.0
76 | : messageOptions.borderRadius,
77 | ),
78 | padding: messageOptions.messagePadding,
79 | child: messageTextBuilder != null
80 | ? messageTextBuilder!(message, previousMessage, nextMessage)
81 | : DefaultMessageText(
82 | message: message,
83 | isOwnMessage: isOwnMessage,
84 | messageOptions: messageOptions,
85 | ),
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/example/lib/samples/theming.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class ThemeSample extends StatefulWidget {
6 | @override
7 | State createState() => ThemeSampleState();
8 | }
9 |
10 | class ThemeSampleState extends State {
11 | List messages = basicSample;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Theme(
16 | data: ThemeData(
17 | appBarTheme: const AppBarTheme(
18 | color: Color(0xff00ff9f),
19 | titleTextStyle: TextStyle(
20 | color: Color(0xff001eff),
21 | fontSize: 20,
22 | fontWeight: FontWeight.bold,
23 | ),
24 | iconTheme: IconThemeData(
25 | color: Color(0xff001eff),
26 | ),
27 | ),
28 | ),
29 | child: Scaffold(
30 | appBar: AppBar(
31 | title: const Text('Theming Example'),
32 | ),
33 | body: Container(
34 | color: const Color(0xff001eff),
35 | child: DashChat(
36 | currentUser: user,
37 | onSend: (ChatMessage m) {
38 | setState(() {
39 | messages.insert(0, m);
40 | });
41 | },
42 | inputOptions: InputOptions(
43 | inputTextStyle: const TextStyle(
44 | color: Color(0xff001eff),
45 | ),
46 | inputDecoration: InputDecoration(
47 | isDense: true,
48 | filled: true,
49 | fillColor: const Color(0xff00b8ff),
50 | contentPadding: const EdgeInsets.only(
51 | left: 18,
52 | top: 10,
53 | bottom: 10,
54 | ),
55 | border: OutlineInputBorder(
56 | borderRadius: BorderRadius.circular(25),
57 | borderSide: const BorderSide(
58 | width: 0,
59 | style: BorderStyle.none,
60 | ),
61 | ),
62 | ),
63 | ),
64 | messages: messages,
65 | messageOptions: const MessageOptions(
66 | containerColor: Color(0xffd600ff),
67 | currentUserContainerColor: Color(0xffbd00ff),
68 | currentUserTextColor: Color(0xfffbf665),
69 | currentUserTimeTextColor: Color.fromRGBO(73, 0, 100, 1),
70 | messagePadding: EdgeInsets.fromLTRB(12, 8, 12, 8),
71 | showTime: true,
72 | spaceWhenAvatarIsHidden: 6,
73 | textColor: Color(0xfffbf665),
74 | timeFontSize: 8,
75 | timePadding: EdgeInsets.only(top: 2),
76 | timeTextColor: Color.fromRGBO(73, 0, 100, 1),
77 | ),
78 | messageListOptions: MessageListOptions(
79 | dateSeparatorBuilder: (date) => DefaultDateSeparator(
80 | date: date,
81 | textStyle: const TextStyle(
82 | color: Color(0xff00ff9f),
83 | fontWeight: FontWeight.bold,
84 | ),
85 | ),
86 | onLoadEarlier: () async {
87 | await Future.delayed(const Duration(seconds: 3));
88 | },
89 | ),
90 | ),
91 | ),
92 | ),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/default_avatar.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultAvatar extends StatelessWidget {
5 | const DefaultAvatar({
6 | required this.user,
7 | this.size = 35,
8 | this.fallbackImage,
9 | this.onPressAvatar,
10 | this.onLongPressAvatar,
11 | });
12 |
13 | /// The URL of the user's profile picture
14 | final ChatUser user;
15 |
16 | /// Size of the avatar
17 | final double size;
18 |
19 | /// Placeholder image in case there is no initials ot he profile image do not load
20 | final ImageProvider? fallbackImage;
21 |
22 | /// Function to call when the user long press on the avatar
23 | final void Function(ChatUser)? onLongPressAvatar;
24 |
25 | /// Function to call when the user press on the avatar
26 | final void Function(ChatUser)? onPressAvatar;
27 |
28 | /// Get the initials of the user
29 | String getInitials() {
30 | return (user.firstName == null || user.firstName!.isEmpty
31 | ? ''
32 | : user.firstName![0]) +
33 | (user.lastName == null || user.lastName!.isEmpty
34 | ? ''
35 | : user.lastName![0]);
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | return GestureDetector(
41 | onTap: onPressAvatar != null ? () => onPressAvatar!(user) : null,
42 | onLongPress:
43 | onLongPressAvatar != null ? () => onLongPressAvatar!(user) : null,
44 | child: Padding(
45 | padding: const EdgeInsets.symmetric(
46 | horizontal: 10,
47 | ),
48 | child: SizedBox(
49 | height: size,
50 | width: size,
51 | child: Stack(
52 | alignment: Alignment.center,
53 | children: [
54 | ClipOval(
55 | child: Container(
56 | color: Colors.grey[200],
57 | child: getInitials().isNotEmpty
58 | ? Center(
59 | child: Text(
60 | getInitials(),
61 | style: TextStyle(
62 | color: Colors.black,
63 | fontSize: size * 0.35,
64 | ),
65 | ),
66 | )
67 | : Image(
68 | image: fallbackImage ??
69 | const AssetImage(
70 | 'assets/profile_placeholder.png',
71 | package: 'dash_chat_2',
72 | ),
73 | ),
74 | ),
75 | ),
76 | if (user.profileImage != null && user.profileImage!.isNotEmpty)
77 | Center(
78 | child: ClipOval(
79 | child: FadeInImage(
80 | width: size,
81 | height: size,
82 | fit: BoxFit.cover,
83 | image: getImageProvider(user.profileImage!),
84 | placeholder: fallbackImage ??
85 | const AssetImage(
86 | 'assets/profile_placeholder.png',
87 | package: 'dash_chat_2',
88 | ),
89 | ),
90 | ),
91 | ),
92 | ],
93 | ),
94 | ),
95 | ),
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/example/lib/samples/mention.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 | import 'package:examples/data.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class MentionSample extends StatefulWidget {
6 | @override
7 | State createState() => MentionSampleState();
8 | }
9 |
10 | class MentionSampleState extends State {
11 | List messages = mentionSample;
12 | List mentions = [];
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: const Text('Mention example'),
19 | ),
20 | body: DashChat(
21 | currentUser: user,
22 | onSend: (ChatMessage m) {
23 | m.mentions = mentions;
24 | setState(() {
25 | messages.insert(0, m);
26 | mentions = [];
27 | });
28 | },
29 | messages: messages,
30 | messageListOptions: MessageListOptions(
31 | onLoadEarlier: () async {
32 | await Future.delayed(const Duration(seconds: 3));
33 | },
34 | ),
35 | messageOptions: MessageOptions(
36 | onPressMention: (mention) {
37 | showDialog(
38 | context: context,
39 | builder: (BuildContext context) {
40 | return AlertDialog(
41 | title: Text(mention.title),
42 | content: Text(mention.customProperties.toString()),
43 | );
44 | },
45 | );
46 | },
47 | ),
48 | inputOptions: InputOptions(
49 | onMention: (String trigger, String value,
50 | void Function(String) onMentionClick) {
51 | // Here you would typically do a request to your backend
52 | // to get the correct results to show
53 | // according to the trigger and value
54 | return Future.delayed(
55 | const Duration(milliseconds: 500),
56 | () {
57 | return [
58 | ListTile(
59 | leading: DefaultAvatar(user: user8),
60 | title: Text(user8.getFullName()),
61 | onTap: () {
62 | onMentionClick(user8.getFullName());
63 | mentions.add(
64 | Mention(
65 | title: trigger + user8.getFullName(),
66 | customProperties: {
67 | 'userId': user8.id,
68 | },
69 | ),
70 | );
71 | },
72 | ),
73 | const Divider(),
74 | ListTile(
75 | leading: DefaultAvatar(user: user5),
76 | title: Text(user5.getFullName()),
77 | onTap: () {
78 | onMentionClick(user5.getFullName());
79 | mentions.add(
80 | Mention(
81 | title: trigger + user5.getFullName(),
82 | customProperties: {
83 | 'userId': user5.id,
84 | },
85 | ),
86 | );
87 | },
88 | )
89 | ];
90 | },
91 | );
92 | },
93 | onMentionTriggers: ['@', '#'],
94 | ),
95 | ),
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | examples
27 |
28 |
29 |
30 |
33 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/lib/src/models/input_options.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class InputOptions {
5 | const InputOptions({
6 | this.textController,
7 | this.focusNode,
8 | this.inputTextDirection = TextDirection.ltr,
9 | this.onMention,
10 | this.onMentionTriggers = const ['@'],
11 | this.onTextChange,
12 | this.inputDisabled = false,
13 | this.inputDecoration,
14 | this.textCapitalization = TextCapitalization.none,
15 | this.alwaysShowSend = false,
16 | this.sendOnEnter = false,
17 | this.textInputAction,
18 | this.maxInputLength,
19 | this.leading,
20 | this.trailing,
21 | this.sendButtonBuilder,
22 | this.inputTextStyle,
23 | this.inputToolbarStyle,
24 | this.inputMaxLines = 5,
25 | this.showTraillingBeforeSend = false,
26 | this.inputToolbarPadding = const EdgeInsets.all(8.0),
27 | this.inputToolbarMargin = const EdgeInsets.only(top: 8.0),
28 | this.cursorStyle = const CursorStyle(),
29 | this.autocorrect = true,
30 | });
31 |
32 | /// Function to call when a mention is triggered in the input,
33 | /// ie: typing ' @'
34 | /// You need to return a list of widget that will be shown inside the selection overlay,
35 | /// for instance user ListTiles
36 | final Future> Function(String trigger, String value,
37 | void Function(String value) onMentionClick)? onMention;
38 |
39 | /// The list of string triggers for the onMention callback
40 | /// By default it only includes '@' character
41 | final List onMentionTriggers;
42 |
43 | /// Function to call when the input text change
44 | final void Function(String value)? onTextChange;
45 |
46 | /// Always show the send button, will be hidden when the text is empty otherwise
47 | final bool alwaysShowSend;
48 |
49 | /// Send the message when the user presses the enter key
50 | final bool sendOnEnter;
51 |
52 | /// Builder to create your own send button widget
53 | /// You can use defaultSendButton to only override some variables
54 | final Widget Function(void Function() send)? sendButtonBuilder;
55 |
56 | /// Text controller for the input field
57 | final TextEditingController? textController;
58 |
59 | /// Focus node of the input field
60 | final FocusNode? focusNode;
61 |
62 | /// Use to change the direction of the text
63 | final TextDirection inputTextDirection;
64 |
65 | /// To make the input disabled
66 | final bool inputDisabled;
67 |
68 | /// Input decoration to customize the design of the input
69 | /// You can use defaultInputDecoration to only override some variables
70 | final InputDecoration? inputDecoration;
71 |
72 | /// Use to override the default TextCapitalization
73 | final TextCapitalization textCapitalization;
74 |
75 | /// An action the user has requested the text input control to perform
76 | final TextInputAction? textInputAction;
77 |
78 | /// If you want to limit the length of the text
79 | final int? maxInputLength;
80 |
81 | /// A list of widget to show before the input
82 | final List? leading;
83 |
84 | /// A list of widget to show after the input
85 | final List? trailing;
86 |
87 | /// To customize the text style of the input
88 | final TextStyle? inputTextStyle;
89 |
90 | /// To customize the overall container of the input
91 | final BoxDecoration? inputToolbarStyle;
92 |
93 | /// Max number of visible lines of the input, it will grow until this value and then scroll
94 | final int inputMaxLines;
95 |
96 | /// If [trailing] should be shown before or after the send button
97 | final bool showTraillingBeforeSend;
98 |
99 | /// Padding of the overall container of the input
100 | final EdgeInsets? inputToolbarPadding;
101 |
102 | /// Margin of the overall container of the input
103 | final EdgeInsets? inputToolbarMargin;
104 |
105 | /// Style of the cursor
106 | final CursorStyle cursorStyle;
107 |
108 | /// Whether to enable auto-correction. Defaults to true.
109 | final bool autocorrect;
110 | }
111 |
--------------------------------------------------------------------------------
/lib/src/models/chat_message.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Models}
4 | class ChatMessage {
5 | ChatMessage({
6 | required this.user,
7 | required this.createdAt,
8 | this.isMarkdown = false,
9 | this.text = '',
10 | this.medias,
11 | this.quickReplies,
12 | this.customProperties,
13 | this.mentions,
14 | this.status = MessageStatus.none,
15 | this.replyTo,
16 | });
17 |
18 | /// Create a ChatMessage instance from json data
19 | factory ChatMessage.fromJson(Map jsonData) {
20 | return ChatMessage(
21 | user: ChatUser.fromJson(jsonData['user'] as Map),
22 | createdAt: DateTime.parse(jsonData['createdAt'].toString()).toLocal(),
23 | text: jsonData['text']?.toString() ?? '',
24 | isMarkdown: jsonData['isMarkdown']?.toString() == 'true',
25 | medias: jsonData['medias'] != null
26 | ? (jsonData['medias'] as List)
27 | .map((dynamic media) =>
28 | ChatMedia.fromJson(media as Map))
29 | .toList()
30 | : [],
31 | quickReplies: jsonData['quickReplies'] != null
32 | ? (jsonData['quickReplies'] as List)
33 | .map((dynamic quickReply) =>
34 | QuickReply.fromJson(quickReply as Map))
35 | .toList()
36 | : [],
37 | customProperties: jsonData['customProperties'] as Map?,
38 | mentions: jsonData['mentions'] != null
39 | ? (jsonData['mentions'] as List)
40 | .map((dynamic mention) =>
41 | Mention.fromJson(mention as Map))
42 | .toList()
43 | : [],
44 | status: MessageStatus.parse(jsonData['status'].toString()),
45 | replyTo: jsonData['replyTo'] != null
46 | ? ChatMessage.fromJson(jsonData['replyTo'] as Map)
47 | : null,
48 | );
49 | }
50 |
51 | /// If the message is Markdown formatted then it will be converted to Markdown (by default it will be false)
52 | bool isMarkdown;
53 |
54 | /// Text of the message (optional because you can also just send a media)
55 | String text;
56 |
57 | /// Author of the message
58 | ChatUser user;
59 |
60 | /// List of medias of the message
61 | List? medias;
62 |
63 | /// A list of quick replies that users can use to reply to this message
64 | List? quickReplies;
65 |
66 | /// A list of custom properties to extend the existing ones
67 | /// in case you need to store more things.
68 | /// Can be useful to extend existing features
69 | Map? customProperties;
70 |
71 | /// Date of the message
72 | DateTime createdAt;
73 |
74 | /// Mentioned elements in the message
75 | List? mentions;
76 |
77 | /// Status of the message TODO:
78 | MessageStatus? status;
79 |
80 | /// If the message is a reply of another one TODO:
81 | ChatMessage? replyTo;
82 |
83 | /// Convert a ChatMessage into a json
84 | Map toJson() {
85 | return {
86 | 'user': user.toJson(),
87 | 'createdAt': createdAt.toUtc().toIso8601String(),
88 | 'text': text,
89 | 'medias': medias?.map((ChatMedia media) => media.toJson()).toList(),
90 | 'quickReplies': quickReplies
91 | ?.map((QuickReply quickReply) => quickReply.toJson())
92 | .toList(),
93 | 'customProperties': customProperties,
94 | 'mentions': mentions,
95 | 'status': status.toString(),
96 | 'replyTo': replyTo?.toJson(),
97 | 'isMarkdown': isMarkdown,
98 | };
99 | }
100 | }
101 |
102 | class MessageStatus {
103 | const MessageStatus._internal(this._value);
104 | final String _value;
105 |
106 | @override
107 | String toString() => _value;
108 |
109 | static MessageStatus parse(String value) {
110 | switch (value) {
111 | case 'none':
112 | return MessageStatus.none;
113 | case 'failed':
114 | return MessageStatus.failed;
115 | case 'sent':
116 | return MessageStatus.sent;
117 | case 'read':
118 | return MessageStatus.read;
119 | case 'received':
120 | return MessageStatus.received;
121 | case 'pending':
122 | return MessageStatus.pending;
123 | default:
124 | return MessageStatus.none;
125 | }
126 | }
127 |
128 | static const MessageStatus none = MessageStatus._internal('none');
129 | static const MessageStatus failed = MessageStatus._internal('failed');
130 | static const MessageStatus sent = MessageStatus._internal('sent');
131 | static const MessageStatus read = MessageStatus._internal('read');
132 | static const MessageStatus received = MessageStatus._internal('received');
133 | static const MessageStatus pending = MessageStatus._internal('pending');
134 | }
135 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/default_message_text.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// {@category Default widgets}
4 | class DefaultMessageText extends StatelessWidget {
5 | const DefaultMessageText({
6 | required this.message,
7 | required this.isOwnMessage,
8 | this.messageOptions = const MessageOptions(),
9 | Key? key,
10 | }) : super(key: key);
11 |
12 | /// Message tha contains the text to show
13 | final ChatMessage message;
14 |
15 | /// If the message is from the current user
16 | final bool isOwnMessage;
17 |
18 | /// Options to customize the behaviour and design of the messages
19 | final MessageOptions messageOptions;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Column(
24 | crossAxisAlignment:
25 | isOwnMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
26 | children: [
27 | Wrap(
28 | children: getMessage(context),
29 | ),
30 | if (messageOptions.showTime)
31 | messageOptions.messageTimeBuilder != null
32 | ? messageOptions.messageTimeBuilder!(message, isOwnMessage)
33 | : Padding(
34 | padding: messageOptions.timePadding,
35 | child: Text(
36 | (messageOptions.timeFormat ?? intl.DateFormat('HH:mm'))
37 | .format(message.createdAt),
38 | style: TextStyle(
39 | color: isOwnMessage
40 | ? messageOptions.currentUserTimeTextColor(context)
41 | : messageOptions.timeTextColor(),
42 | fontSize: messageOptions.timeFontSize,
43 | ),
44 | ),
45 | ),
46 | ],
47 | );
48 | }
49 |
50 | List getMessage(BuildContext context) {
51 | if (message.mentions != null && message.mentions!.isNotEmpty) {
52 | String stringRegex = r'([\s\S]*)';
53 | String stringMentionRegex = '';
54 | for (final Mention mention in message.mentions!) {
55 | stringRegex += '(${mention.title})' r'([\s\S]*)';
56 | stringMentionRegex += stringMentionRegex.isEmpty
57 | ? '(${mention.title})'
58 | : '|(${mention.title})';
59 | }
60 | final RegExp mentionRegex = RegExp(stringMentionRegex);
61 | final RegExp regexp = RegExp(stringRegex);
62 |
63 | RegExpMatch? match = regexp.firstMatch(message.text);
64 | if (match != null) {
65 | List res = [];
66 | match
67 | .groups(List.generate(match.groupCount, (int i) => i + 1))
68 | .forEach((String? part) {
69 | Mention? mention;
70 | if (mentionRegex.hasMatch(part!)) {
71 | try {
72 | mention = message.mentions?.firstWhere(
73 | (Mention m) => m.title == part,
74 | );
75 | } catch (e) {
76 | // There is no mention
77 | }
78 | }
79 | if (mention != null) {
80 | res.add(getMention(context, mention));
81 | } else {
82 | res.add(getParsePattern(context, part, message.isMarkdown));
83 | }
84 | });
85 | if (res.isNotEmpty) {
86 | return res;
87 | }
88 | }
89 | }
90 | return [getParsePattern(context, message.text, message.isMarkdown)];
91 | }
92 |
93 | Widget getParsePattern(BuildContext context, String text, bool isMarkdown) {
94 | return isMarkdown
95 | ? MarkdownBody(
96 | data: text,
97 | selectable: true,
98 | styleSheet: messageOptions.markdownStyleSheet,
99 | onTapLink: (String value, String? href, String title) {
100 | if (href != null) {
101 | openLink(href);
102 | } else {
103 | openLink(value);
104 | }
105 | },
106 | )
107 | : ParsedText(
108 | parse: messageOptions.parsePatterns != null
109 | ? messageOptions.parsePatterns!
110 | : defaultParsePatterns,
111 | text: text,
112 | style: TextStyle(
113 | color: isOwnMessage
114 | ? messageOptions.currentUserTextColor(context)
115 | : messageOptions.textColor,
116 | ),
117 | );
118 | }
119 |
120 | Widget getMention(BuildContext context, Mention mention) {
121 | return RichText(
122 | text: TextSpan(
123 | text: mention.title,
124 | recognizer: TapGestureRecognizer()
125 | ..onTap = () => messageOptions.onPressMention != null
126 | ? messageOptions.onPressMention!(mention)
127 | : null,
128 | style: TextStyle(
129 | color: isOwnMessage
130 | ? messageOptions.currentUserTextColor(context)
131 | : messageOptions.textColor,
132 | decoration: TextDecoration.none,
133 | fontWeight: FontWeight.w600,
134 | ),
135 | ),
136 | );
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/media_container.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class MediaContainer extends StatelessWidget {
5 | const MediaContainer({
6 | required this.message,
7 | required this.isOwnMessage,
8 | this.messageOptions = const MessageOptions(),
9 | Key? key,
10 | }) : super(key: key);
11 |
12 | /// Message that contains the media to show
13 | final ChatMessage message;
14 |
15 | /// If the message is from the current user
16 | final bool isOwnMessage;
17 |
18 | /// Options to customize the behaviour and design of the messages
19 | final MessageOptions messageOptions;
20 |
21 | /// Get the right media widget according to its type
22 | Widget _getMedia(
23 | BuildContext context,
24 | ChatMedia media,
25 | double? height,
26 | double? width,
27 | ) {
28 | final Widget loading = Container(
29 | width: 15,
30 | height: 15,
31 | margin: const EdgeInsets.all(10),
32 | child: const CircularProgressIndicator(),
33 | );
34 | switch (media.type) {
35 | case MediaType.video:
36 | return Stack(
37 | alignment: AlignmentDirectional.bottomEnd,
38 | children: [
39 | VideoPlayer(url: media.url, key: Key(media.url)),
40 | if (media.isUploading) loading
41 | ],
42 | );
43 | case MediaType.image:
44 | return Stack(
45 | alignment: AlignmentDirectional.bottomEnd,
46 | children: [
47 | Image(
48 | height: height,
49 | width: width,
50 | fit: BoxFit.cover,
51 | alignment: isOwnMessage ? Alignment.topRight : Alignment.topLeft,
52 | image: getImageProvider(media.url),
53 | ),
54 | if (media.isUploading) loading
55 | ],
56 | );
57 | default:
58 | return TextContainer(
59 | isOwnMessage: isOwnMessage,
60 | messageOptions: messageOptions,
61 | message: message,
62 | messageTextBuilder: (ChatMessage m, ChatMessage? p, ChatMessage? n) {
63 | return Row(
64 | children: [
65 | Padding(
66 | padding: const EdgeInsets.only(right: 8.0),
67 | child: !media.isUploading
68 | ? Icon(
69 | Icons.description,
70 | size: 18,
71 | color: isOwnMessage
72 | ? messageOptions.currentUserTextColor(context)
73 | : messageOptions.textColor,
74 | )
75 | : loading,
76 | ),
77 | Flexible(
78 | child: Text(
79 | media.fileName,
80 | style: TextStyle(
81 | decoration: TextDecoration.underline,
82 | color: isOwnMessage
83 | ? messageOptions.currentUserTextColor(context)
84 | : messageOptions.textColor,
85 | ),
86 | ),
87 | ),
88 | ],
89 | );
90 | },
91 | );
92 | }
93 | }
94 |
95 | @override
96 | Widget build(BuildContext context) {
97 | if (message.medias != null && message.medias!.isNotEmpty) {
98 | final List media = message.medias!;
99 | return Wrap(
100 | alignment: isOwnMessage ? WrapAlignment.end : WrapAlignment.start,
101 | children: media.map(
102 | (ChatMedia m) {
103 | final double gallerySize =
104 | (MediaQuery.of(context).size.width * 0.7) / 2 - 5;
105 | final bool isImage = m.type == MediaType.image;
106 | return Container(
107 | color: Colors.transparent,
108 | margin: const EdgeInsets.only(top: 5, right: 5),
109 | width: media.length > 1 && isImage ? gallerySize : null,
110 | height: media.length > 1 && isImage ? gallerySize : null,
111 | constraints: BoxConstraints(
112 | maxHeight: MediaQuery.of(context).size.height * 0.5,
113 | maxWidth: MediaQuery.of(context).size.width * 0.7,
114 | ),
115 | child: GestureDetector(
116 | onTap: messageOptions.onTapMedia != null
117 | ? () => messageOptions.onTapMedia!(m)
118 | : null,
119 | child: ClipRRect(
120 | borderRadius: BorderRadius.circular(8.0),
121 | child: ColorFiltered(
122 | colorFilter: ColorFilter.mode(
123 | m.isUploading
124 | ? Colors.white54
125 | : Colors.white.withOpacity(
126 | 0.1,
127 | ), // Because transparent is causing an issue on flutter web
128 | BlendMode.srcATop,
129 | ),
130 | child: _getMedia(
131 | context,
132 | m,
133 | media.length > 1 ? gallerySize : null,
134 | media.length > 1 ? gallerySize : null,
135 | ),
136 | ),
137 | ),
138 | ),
139 | );
140 | },
141 | ).toList(),
142 | );
143 | }
144 | return const SizedBox();
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_row/message_row.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class MessageRow extends StatelessWidget {
5 | const MessageRow({
6 | required this.message,
7 | required this.currentUser,
8 | this.previousMessage,
9 | this.nextMessage,
10 | this.isAfterDateSeparator = false,
11 | this.isBeforeDateSeparator = false,
12 | this.messageOptions = const MessageOptions(),
13 | Key? key,
14 | }) : super(key: key);
15 |
16 | /// Current message to show
17 | final ChatMessage message;
18 |
19 | /// Previous message in the list
20 | final ChatMessage? previousMessage;
21 |
22 | /// Next message in the list
23 | final ChatMessage? nextMessage;
24 |
25 | /// Current user of the chat
26 | final ChatUser currentUser;
27 |
28 | /// If the message is preceded by a date separator
29 | final bool isAfterDateSeparator;
30 |
31 | /// If the message is before a date separator
32 | final bool isBeforeDateSeparator;
33 |
34 | /// Options to customize the behaviour and design of the messages
35 | final MessageOptions messageOptions;
36 |
37 | /// Get the avatar widget
38 | Widget getAvatar() {
39 | return messageOptions.avatarBuilder != null
40 | ? messageOptions.avatarBuilder!(
41 | message.user,
42 | messageOptions.onPressAvatar,
43 | messageOptions.onLongPressAvatar,
44 | )
45 | : DefaultAvatar(
46 | user: message.user,
47 | onLongPressAvatar: messageOptions.onLongPressAvatar,
48 | onPressAvatar: messageOptions.onPressAvatar,
49 | );
50 | }
51 |
52 | @override
53 | Widget build(BuildContext context) {
54 | final bool isOwnMessage = message.user.id == currentUser.id;
55 | bool isPreviousSameAuthor = false;
56 | bool isNextSameAuthor = false;
57 | if (previousMessage != null &&
58 | previousMessage!.user.id == message.user.id) {
59 | isPreviousSameAuthor = true;
60 | }
61 | if (nextMessage != null && nextMessage!.user.id == message.user.id) {
62 | isNextSameAuthor = true;
63 | }
64 |
65 | return Container(
66 | margin: isAfterDateSeparator
67 | ? EdgeInsets.zero
68 | : isPreviousSameAuthor
69 | ? messageOptions.marginSameAuthor
70 | : messageOptions.marginDifferentAuthor,
71 | child: Row(
72 | crossAxisAlignment: CrossAxisAlignment.end,
73 | mainAxisAlignment:
74 | isOwnMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
75 | children: [
76 | if (messageOptions.showOtherUsersAvatar)
77 | Opacity(
78 | opacity:
79 | !isOwnMessage && (!isNextSameAuthor || isBeforeDateSeparator)
80 | ? 1
81 | : 0,
82 | child: getAvatar(),
83 | ),
84 | if (!messageOptions.showOtherUsersAvatar)
85 | SizedBox(width: messageOptions.spaceWhenAvatarIsHidden),
86 | GestureDetector(
87 | onLongPress: messageOptions.onLongPressMessage != null
88 | ? () => messageOptions.onLongPressMessage!(message)
89 | : null,
90 | onTap: messageOptions.onPressMessage != null
91 | ? () => messageOptions.onPressMessage!(message)
92 | : null,
93 | child: ConstrainedBox(
94 | constraints: BoxConstraints(
95 | maxWidth: messageOptions.maxWidth ??
96 | MediaQuery.of(context).size.width * 0.7,
97 | ),
98 | child: Column(
99 | crossAxisAlignment: isOwnMessage
100 | ? CrossAxisAlignment.end
101 | : CrossAxisAlignment.start,
102 | mainAxisAlignment: MainAxisAlignment.end,
103 | children: [
104 | if (messageOptions.top != null)
105 | messageOptions.top!(message, previousMessage, nextMessage),
106 | if (!isOwnMessage &&
107 | messageOptions.showOtherUsersName &&
108 | (!isPreviousSameAuthor || isAfterDateSeparator))
109 | messageOptions.userNameBuilder != null
110 | ? messageOptions.userNameBuilder!(message.user)
111 | : DefaultUserName(user: message.user),
112 | if (message.medias != null &&
113 | message.medias!.isNotEmpty &&
114 | messageOptions.textBeforeMedia)
115 | messageOptions.messageMediaBuilder != null
116 | ? messageOptions.messageMediaBuilder!(
117 | message, previousMessage, nextMessage)
118 | : MediaContainer(
119 | message: message,
120 | isOwnMessage: isOwnMessage,
121 | messageOptions: messageOptions,
122 | ),
123 | if (message.text.isNotEmpty)
124 | TextContainer(
125 | messageOptions: messageOptions,
126 | message: message,
127 | previousMessage: previousMessage,
128 | nextMessage: nextMessage,
129 | isOwnMessage: isOwnMessage,
130 | isNextSameAuthor: isNextSameAuthor,
131 | isPreviousSameAuthor: isPreviousSameAuthor,
132 | isAfterDateSeparator: isAfterDateSeparator,
133 | isBeforeDateSeparator: isBeforeDateSeparator,
134 | messageTextBuilder: messageOptions.messageTextBuilder,
135 | ),
136 | if (message.medias != null &&
137 | message.medias!.isNotEmpty &&
138 | !messageOptions.textBeforeMedia)
139 | messageOptions.messageMediaBuilder != null
140 | ? messageOptions.messageMediaBuilder!(
141 | message, previousMessage, nextMessage)
142 | : MediaContainer(
143 | message: message,
144 | isOwnMessage: isOwnMessage,
145 | messageOptions: messageOptions,
146 | ),
147 | if (messageOptions.bottom != null)
148 | messageOptions.bottom!(
149 | message, previousMessage, nextMessage),
150 | ],
151 | ),
152 | ),
153 | ),
154 | if (messageOptions.showCurrentUserAvatar)
155 | Opacity(
156 | opacity: isOwnMessage && !isNextSameAuthor ? 1 : 0,
157 | child: getAvatar(),
158 | ),
159 | if (!messageOptions.showCurrentUserAvatar)
160 | SizedBox(width: messageOptions.spaceWhenAvatarIsHidden),
161 | ],
162 | ),
163 | );
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/example/lib/data.dart:
--------------------------------------------------------------------------------
1 | import 'package:dash_chat_2/dash_chat_2.dart';
2 |
3 | String profileImage =
4 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/1-intro-photo-final.jpeg?alt=media&token=daf78997-d8f0-49d1-9120-a9380bde48b5';
5 |
6 | // We have all the possibilities for users
7 | ChatUser user = ChatUser(id: '0');
8 | ChatUser user1 = ChatUser(id: '1');
9 | ChatUser user2 = ChatUser(id: '2', firstName: 'Niki Lauda');
10 | ChatUser user3 = ChatUser(id: '3', lastName: 'Clark');
11 | ChatUser user4 = ChatUser(id: '4', profileImage: profileImage);
12 | ChatUser user5 = ChatUser(id: '5', firstName: 'Charles', lastName: 'Leclerc');
13 | ChatUser user6 =
14 | ChatUser(id: '6', firstName: 'Max', profileImage: profileImage);
15 | ChatUser user7 =
16 | ChatUser(id: '7', lastName: 'Toto', profileImage: profileImage);
17 | ChatUser user8 = ChatUser(
18 | id: '8', firstName: 'Toto', lastName: 'Clark', profileImage: profileImage);
19 |
20 | List allUsersSample = [
21 | ChatMessage(
22 | text: 'Test',
23 | user: user1,
24 | createdAt: DateTime(2021, 01, 30, 16, 45),
25 | ),
26 | ChatMessage(
27 | text: 'Test',
28 | user: user2,
29 | createdAt: DateTime(2021, 01, 30, 16, 45),
30 | ),
31 | ChatMessage(
32 | text: 'Test',
33 | user: user3,
34 | createdAt: DateTime(2021, 01, 30, 16, 45),
35 | ),
36 | ChatMessage(
37 | text: 'Test',
38 | user: user4,
39 | createdAt: DateTime(2021, 01, 30, 16, 45),
40 | ),
41 | ChatMessage(
42 | text: 'Test',
43 | user: user5,
44 | createdAt: DateTime(2021, 01, 30, 16, 45),
45 | ),
46 | ChatMessage(
47 | text: 'Test',
48 | user: user6,
49 | createdAt: DateTime(2021, 01, 30, 16, 45),
50 | ),
51 | ChatMessage(
52 | text: 'Test',
53 | user: user7,
54 | createdAt: DateTime(2021, 01, 30, 16, 45),
55 | ),
56 | ChatMessage(
57 | text: 'Test',
58 | user: user8,
59 | createdAt: DateTime(2021, 01, 30, 16, 45),
60 | ),
61 | ];
62 |
63 | List basicSample = [
64 | ChatMessage(
65 | text: 'google.com hello you @Marc is it &you okay?',
66 | user: user2,
67 | createdAt: DateTime(2021, 01, 31, 16, 45),
68 | mentions: [
69 | Mention(title: '@Marc'),
70 | Mention(title: '&you'),
71 | ],
72 | ),
73 | ChatMessage(
74 | text: 'google.com',
75 | user: user2,
76 | createdAt: DateTime(2021, 01, 30, 16, 45),
77 | ),
78 | ChatMessage(
79 | text: "Oh what's up guys?",
80 | user: user2,
81 | createdAt: DateTime(2021, 01, 30, 16, 45),
82 | ),
83 | ChatMessage(
84 | text: 'How you doin?',
85 | user: user8,
86 | createdAt: DateTime(2021, 01, 30, 16, 34),
87 | ),
88 | ChatMessage(
89 | isMarkdown: true,
90 | text:
91 | "```dart\nvoid main() {\n print('Hello World');\n}\n```\nThe above code will print \"Hello World\" to the console when run.\n\nHere's a breakdown of the code:\n\n* The `main()` function is the entry point of the program. It's where execution begins.\n* `print('Hello World')` prints \"Hello World\" to the console. The `print()` function is a built-in function in Dart that outputs data to the console.\n\nYou can run this code by creating a new Dart file (e.g., `hello_world.dart`) and pasting the code into it. Then, open a terminal window, navigate to the directory where the file is saved, and run the following command:\n\n```\ndart hello_world.dart\n```\n\nThis will compile and run the Dart program, and you should see \"Hello World\" printed to the console. Know more: www.google.com ",
92 | user: user2,
93 | createdAt: DateTime(2021, 01, 30, 15, 50),
94 | ),
95 | ChatMessage(
96 | text: 'Hey!',
97 | user: user,
98 | createdAt: DateTime(2021, 01, 30, 15, 50),
99 | ),
100 | ChatMessage(
101 | text: 'Hey!',
102 | user: user,
103 | createdAt: DateTime(2021, 01, 28, 15, 50),
104 | ),
105 | ChatMessage(
106 | text: 'Hey!',
107 | user: user,
108 | createdAt: DateTime(2021, 01, 28, 15, 50),
109 | ),
110 | ];
111 |
112 | List media = [
113 | ChatMessage(
114 | medias: [
115 | ChatMedia(
116 | url:
117 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/memes%2F155512641_3864499247004975_4028017188079714246_n.jpg?alt=media&token=0b335455-93ed-4529-9055-9a2c741e0189',
118 | type: MediaType.image,
119 | fileName: 'image.png',
120 | isUploading: true,
121 | ),
122 | ChatMedia(
123 | url:
124 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/memes%2F155512641_3864499247004975_4028017188079714246_n.jpg?alt=media&token=0b335455-93ed-4529-9055-9a2c741e0189',
125 | type: MediaType.image,
126 | fileName: 'image.png',
127 | ),
128 | ChatMedia(
129 | url:
130 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/chat_medias%2F2GFlPkj94hKCqonpEdf1%2F20210526_162318.mp4?alt=media&token=01b814b9-d93a-4bf1-8be1-cf9a49058f97',
131 | type: MediaType.video,
132 | fileName: 'video.mp4',
133 | isUploading: false,
134 | ),
135 | ChatMedia(
136 | url:
137 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/chat_medias%2F2GFlPkj94hKCqonpEdf1%2F20210526_162318.mp4?alt=media&token=01b814b9-d93a-4bf1-8be1-cf9a49058f97',
138 | type: MediaType.video,
139 | fileName: 'video.mp4',
140 | isUploading: false,
141 | ),
142 | ChatMedia(
143 | url:
144 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/memes%2F155512641_3864499247004975_4028017188079714246_n.jpg?alt=media&token=0b335455-93ed-4529-9055-9a2c741e0189',
145 | type: MediaType.file,
146 | fileName: 'image.png',
147 | ),
148 | ChatMedia(
149 | url:
150 | 'https://firebasestorage.googleapis.com/v0/b/molteo-40978.appspot.com/o/memes%2F155512641_3864499247004975_4028017188079714246_n.jpg?alt=media&token=0b335455-93ed-4529-9055-9a2c741e0189',
151 | type: MediaType.image,
152 | fileName: 'image.png',
153 | )
154 | ],
155 | user: user3,
156 | createdAt: DateTime(2021, 01, 30, 16, 34),
157 | ),
158 | ];
159 |
160 | List quickReplies = [
161 | ChatMessage(
162 | text: 'How you doin?',
163 | user: user3,
164 | createdAt: DateTime.now(),
165 | quickReplies: [
166 | QuickReply(title: 'Great!'),
167 | QuickReply(title: 'Awesome'),
168 | ],
169 | ),
170 | ];
171 |
172 | List mentionSample = [
173 | ChatMessage(
174 | text: 'Hello @Niki, you should check #channel',
175 | user: user2,
176 | createdAt: DateTime(2021, 01, 31, 16, 45),
177 | mentions: [
178 | Mention(title: '@Niki', customProperties: {'userId': user5.id}),
179 | Mention(title: '#channel'),
180 | ],
181 | ),
182 | ChatMessage(
183 | text: "Oh what's up guys?",
184 | user: user5,
185 | createdAt: DateTime(2021, 01, 30, 16, 45),
186 | ),
187 | ];
188 |
189 | List d = [];
190 |
--------------------------------------------------------------------------------
/lib/src/models/message_options.dart:
--------------------------------------------------------------------------------
1 | part of '../../dash_chat_2.dart';
2 |
3 | /// {@category Customization}
4 | class MessageOptions {
5 | const MessageOptions({
6 | this.showCurrentUserAvatar = false,
7 | this.showOtherUsersAvatar = true,
8 | this.showOtherUsersName = true,
9 | this.userNameBuilder,
10 | this.avatarBuilder,
11 | this.onPressAvatar,
12 | this.onLongPressAvatar,
13 | this.onLongPressMessage,
14 | this.onPressMessage,
15 | this.onPressMention,
16 | Color? currentUserContainerColor,
17 | Color? currentUserTextColor,
18 | this.containerColor = const Color(0xFFF5F5F5),
19 | this.textColor = Colors.black,
20 | this.messagePadding = const EdgeInsets.all(11),
21 | this.maxWidth,
22 | this.messageDecorationBuilder,
23 | this.top,
24 | this.bottom,
25 | this.messageRowBuilder,
26 | this.messageTextBuilder,
27 | this.parsePatterns,
28 | this.textBeforeMedia = true,
29 | this.onTapMedia,
30 | this.showTime = false,
31 | this.timeFormat,
32 | this.messageTimeBuilder,
33 | this.messageMediaBuilder,
34 | this.borderRadius = 18.0,
35 | Color? currentUserTimeTextColor,
36 | this.marginDifferentAuthor = const EdgeInsets.only(top: 15),
37 | this.marginSameAuthor = const EdgeInsets.only(top: 2),
38 | this.spaceWhenAvatarIsHidden = 10.0,
39 | this.timeFontSize = 10.0,
40 | this.timePadding = const EdgeInsets.only(top: 5),
41 | this.markdownStyleSheet,
42 | Color? timeTextColor,
43 | }) : _currentUserContainerColor = currentUserContainerColor,
44 | _currentUserTextColor = currentUserTextColor,
45 | _currentUserTimeTextColor = currentUserTimeTextColor,
46 | _timeTextColor = timeTextColor;
47 |
48 | /// Format of the time if [showTime] is true
49 | /// Default to: DateFormat('HH:mm')
50 | final intl.DateFormat? timeFormat;
51 |
52 | /// If you want to show the time under the text of each message
53 | final bool showTime;
54 |
55 | /// If you want to show the avatar of the current user
56 | final bool showCurrentUserAvatar;
57 |
58 | /// If you want to show the avatar of the other users
59 | final bool showOtherUsersAvatar;
60 |
61 | /// If you want to show the name of the other users above the messages
62 | /// Useful in group chats
63 | final bool showOtherUsersName;
64 |
65 | /// If you want to create your own userName widget when [showOtherUsersName] is true
66 | /// You can use DefaultUserName to only override some variables
67 | final Widget Function(ChatUser user)? userNameBuilder;
68 |
69 | /// Builder to create your own avatar
70 | /// You can use DefaultAvatar to only override some variables
71 | final Widget Function(
72 | ChatUser, Function? onPressAvatar, Function? onLongPressAvatar)?
73 | avatarBuilder;
74 |
75 | /// Function to call when the user press on an avatar
76 | final Function(ChatUser)? onPressAvatar;
77 |
78 | /// Function to call when the user long press on an avatar
79 | final Function(ChatUser)? onLongPressAvatar;
80 |
81 | /// Function to call when the user long press on a message
82 | final Function(ChatMessage)? onLongPressMessage;
83 |
84 | /// Function to call when the user press on a message
85 | final Function(ChatMessage)? onPressMessage;
86 |
87 | /// Function to call when the user press on a message mention
88 | final Function(Mention)? onPressMention;
89 |
90 | /// Color of the current user chat bubbles
91 | ///
92 | /// Default to: `Theme.of(context).primaryColor`
93 | Color currentUserContainerColor(BuildContext context) {
94 | return _currentUserContainerColor ?? Theme.of(context).primaryColor;
95 | }
96 |
97 | /// Used to calculate [currentUserContainerColor]
98 | final Color? _currentUserContainerColor;
99 |
100 | /// Color of the current user text in chat bubbles
101 | ///
102 | /// Default to: `Theme.of(context).colorScheme.onPrimary`
103 | Color currentUserTextColor(BuildContext context) {
104 | return _currentUserTextColor ?? Theme.of(context).colorScheme.onPrimary;
105 | }
106 |
107 | /// Used to calculate [currentUserTextColor]
108 | final Color? _currentUserTextColor;
109 |
110 | /// Color of current user time text in chat bubbles
111 | ///
112 | /// Default to: `currentUserTextColor`
113 | Color currentUserTimeTextColor(BuildContext context) {
114 | return _currentUserTimeTextColor ?? currentUserTextColor(context);
115 | }
116 |
117 | /// Used to calculate [currentUserTimeTextColor]
118 | final Color? _currentUserTimeTextColor;
119 |
120 | /// Color of the other users chat bubbles
121 | ///
122 | /// Default to: `Colors.grey.shade100`
123 | final Color containerColor;
124 |
125 | /// Color of the other users text in chat bubbles
126 | ///
127 | /// Default to: `Colors.black`
128 | final Color textColor;
129 |
130 | /// Color of other users time text in chat bubbles
131 | ///
132 | /// Default to: `textColor`
133 |
134 | Color timeTextColor() {
135 | return _timeTextColor ?? textColor;
136 | }
137 |
138 | /// Used to calculate [timeTextColor]
139 | final Color? _timeTextColor;
140 |
141 | /// Builder to create the entire message row yourself
142 | final Widget Function(
143 | ChatMessage message,
144 | ChatMessage? previousMessage,
145 | ChatMessage? nextMessage,
146 | bool isAfterDateSeparator,
147 | bool isBeforeDateSeparator,
148 | )? messageRowBuilder;
149 |
150 | /// Builder to create own message text widget
151 | final Widget Function(ChatMessage message, ChatMessage? previousMessage,
152 | ChatMessage? nextMessage)? messageTextBuilder;
153 |
154 | /// Builder to create your own media container widget
155 | final Widget Function(ChatMessage message, ChatMessage? previousMessage,
156 | ChatMessage? nextMessage)? messageMediaBuilder;
157 |
158 | /// Builder to create your own time widget
159 | /// (shown under the text when [showTime] is true)
160 | final Widget Function(ChatMessage message, bool isOwnMessage)?
161 | messageTimeBuilder;
162 |
163 | /// List of MatchText using flutter_parsed_text library
164 | /// to parse and customize accordingly some part of the text
165 | /// By default ParsedType.URL is set and will use launchUrl to open the link
166 | final List? parsePatterns;
167 |
168 | /// Padding around the text in chat bubbles
169 | ///
170 | /// Default to: `EdgeInsets.all(11)`
171 | final EdgeInsets messagePadding;
172 |
173 | /// Max message width
174 | ///
175 | /// Default to: `MediaQuery.of(context).size.width * 0.7`
176 | final double? maxWidth;
177 |
178 | /// When a message have both an text and a list of media
179 | /// it will determine which one th show first
180 | final bool textBeforeMedia;
181 |
182 | /// To create your own BoxDecoration fot the chat bubble
183 | /// You can use defaultMessageDecoration to only override some variables
184 | final BoxDecoration Function(
185 | ChatMessage message,
186 | ChatMessage? previousMessage,
187 | ChatMessage? nextMessage)? messageDecorationBuilder;
188 |
189 | /// A widget to show above the chat bubble
190 | final Widget Function(ChatMessage message, ChatMessage? previousMessage,
191 | ChatMessage? nextMessage)? top;
192 |
193 | /// A widget to show under the chat bubble
194 | final Widget Function(ChatMessage message, ChatMessage? previousMessage,
195 | ChatMessage? nextMessage)? bottom;
196 |
197 | /// Function to call when the user clicks on a media
198 | /// Will not work with the default video player
199 | final void Function(ChatMedia media)? onTapMedia;
200 |
201 | /// Border radius of the chat bubbles
202 | ///
203 | /// Default to: `18.0`
204 | final double borderRadius;
205 |
206 | /// Margin around the chat bubble when previous author is different
207 | ///
208 | /// Default to: `const EdgeInsets.only(top: 15)`
209 | final EdgeInsets marginDifferentAuthor;
210 |
211 | /// Margin around the chat bubble when previous author is the same
212 | ///
213 | /// Default to: `const EdgeInsets.only(top: 2)`
214 | final EdgeInsets marginSameAuthor;
215 |
216 | /// Space between chat bubble and edge of the list when avatar is hidden via [showOtherUsersAvatar] or [showCurrentUserAvatar]
217 | ///
218 | /// Default to: `10.0`
219 | final double spaceWhenAvatarIsHidden;
220 |
221 | /// Font size of the time text in chat bubbles
222 | ///
223 | /// Default to: `10.0`
224 | final double timeFontSize;
225 |
226 | /// Space between time and message text in chat bubbles
227 | ///
228 | /// Default to: `const EdgeInsets.only(top: 5)`
229 | final EdgeInsets timePadding;
230 |
231 | /// Stylesheet for markdown message rendering
232 | final MarkdownStyleSheet? markdownStyleSheet;
233 | }
234 |
--------------------------------------------------------------------------------
/lib/src/widgets/input_toolbar/input_toolbar.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class InputToolbar extends StatefulWidget {
5 | const InputToolbar({
6 | required this.currentUser,
7 | required this.onSend,
8 | this.inputOptions = const InputOptions(),
9 | Key? key,
10 | }) : super(key: key);
11 |
12 | /// Options to custom the toolbar
13 | final InputOptions inputOptions;
14 |
15 | /// Function to call when the message is sent (click on the send button)
16 | final Function(ChatMessage) onSend;
17 |
18 | /// Current user using the chat
19 | final ChatUser currentUser;
20 |
21 | @override
22 | State createState() => InputToolbarState();
23 | }
24 |
25 | class InputToolbarState extends State
26 | with WidgetsBindingObserver {
27 | late TextEditingController textController;
28 | OverlayEntry? _overlayEntry;
29 | int currentMentionIndex = -1;
30 | String currentTrigger = '';
31 | late FocusNode focusNode;
32 |
33 | @override
34 | void initState() {
35 | textController =
36 | widget.inputOptions.textController ?? TextEditingController();
37 | focusNode = widget.inputOptions.focusNode ?? FocusNode();
38 | focusNode.addListener(() {
39 | if (!focusNode.hasFocus) {
40 | _clearOverlay();
41 | }
42 | });
43 | WidgetsBinding.instance.addObserver(this);
44 | super.initState();
45 | }
46 |
47 | @override
48 | void didChangeMetrics() {
49 | final double bottomInset = View.of(context).viewInsets.bottom;
50 | final bool isKeyboardActive = bottomInset > 0.0;
51 | if (!isKeyboardActive) {
52 | _clearOverlay();
53 | }
54 | }
55 |
56 | @override
57 | void dispose() {
58 | WidgetsBinding.instance.removeObserver(this);
59 | _clearOverlay();
60 | super.dispose();
61 | }
62 |
63 | @override
64 | Widget build(BuildContext context) {
65 | return SafeArea(
66 | top: false,
67 | child: Container(
68 | padding: widget.inputOptions.inputToolbarPadding,
69 | margin: widget.inputOptions.inputToolbarMargin,
70 | decoration: widget.inputOptions.inputToolbarStyle,
71 | child: Row(
72 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
73 | crossAxisAlignment: CrossAxisAlignment.center,
74 | children: [
75 | if (widget.inputOptions.leading != null)
76 | ...widget.inputOptions.leading!,
77 | Expanded(
78 | child: Directionality(
79 | textDirection: widget.inputOptions.inputTextDirection,
80 | child: TextField(
81 | focusNode: focusNode,
82 | controller: textController,
83 | enabled: !widget.inputOptions.inputDisabled,
84 | textCapitalization: widget.inputOptions.textCapitalization,
85 | textInputAction: widget.inputOptions.textInputAction,
86 | decoration: widget.inputOptions.inputDecoration ??
87 | defaultInputDecoration(),
88 | maxLength: widget.inputOptions.maxInputLength,
89 | minLines: 1,
90 | maxLines: widget.inputOptions.sendOnEnter
91 | ? 1
92 | : widget.inputOptions.inputMaxLines,
93 | cursorColor: widget.inputOptions.cursorStyle.color,
94 | cursorWidth: widget.inputOptions.cursorStyle.width,
95 | showCursor: !widget.inputOptions.cursorStyle.hide,
96 | style: widget.inputOptions.inputTextStyle,
97 | onSubmitted: (String value) {
98 | if (widget.inputOptions.sendOnEnter) {
99 | _sendMessage();
100 | }
101 | },
102 | onChanged: (String value) async {
103 | setState(() {});
104 | if (widget.inputOptions.onTextChange != null) {
105 | widget.inputOptions.onTextChange!(value);
106 | }
107 | WidgetsBinding.instance.addPostFrameCallback((_) async {
108 | if (widget.inputOptions.onMention != null) {
109 | await _checkMentions(value);
110 | }
111 | });
112 | },
113 | autocorrect: widget.inputOptions.autocorrect,
114 | ),
115 | ),
116 | ),
117 | if (widget.inputOptions.trailing != null &&
118 | widget.inputOptions.showTraillingBeforeSend)
119 | ...widget.inputOptions.trailing!,
120 | if (widget.inputOptions.alwaysShowSend ||
121 | textController.text.isNotEmpty)
122 | widget.inputOptions.sendButtonBuilder != null
123 | ? widget.inputOptions.sendButtonBuilder!(_sendMessage)
124 | : defaultSendButton(color: Theme.of(context).primaryColor)(
125 | _sendMessage,
126 | ),
127 | if (widget.inputOptions.trailing != null &&
128 | !widget.inputOptions.showTraillingBeforeSend)
129 | ...widget.inputOptions.trailing!,
130 | ],
131 | ),
132 | ),
133 | );
134 | }
135 |
136 | Future _checkMentions(String text) async {
137 | bool hasMatch = false;
138 | for (final String trigger in widget.inputOptions.onMentionTriggers) {
139 | final RegExp regexp = RegExp(r'(?])' + trigger + r'([^\s<>]+)$');
140 | if (regexp.hasMatch(text)) {
141 | hasMatch = true;
142 | currentMentionIndex = textController.text.indexOf(regexp);
143 | currentTrigger = trigger;
144 | List children = await widget.inputOptions.onMention!(
145 | trigger,
146 | regexp.firstMatch(text)!.group(1)!,
147 | _onMentionClick,
148 | );
149 | _showMentionModal(children);
150 | }
151 | }
152 | if (!hasMatch) {
153 | _clearOverlay();
154 | }
155 | }
156 |
157 | void _onMentionClick(String value) {
158 | textController.text = textController.text.replaceRange(
159 | currentMentionIndex,
160 | textController.text.length,
161 | currentTrigger + value,
162 | );
163 | textController.selection = TextSelection.collapsed(
164 | offset: textController.text.length,
165 | );
166 | _clearOverlay();
167 | }
168 |
169 | void _clearOverlay() {
170 | if (_overlayEntry != null && _overlayEntry!.mounted) {
171 | _overlayEntry?.remove();
172 | _overlayEntry?.dispose();
173 | }
174 | }
175 |
176 | void _showMentionModal(List children) {
177 | final OverlayState overlay = Overlay.of(context);
178 | final RenderBox renderBox = context.findRenderObject() as RenderBox;
179 | final Offset topLeftCornerOffset = renderBox.localToGlobal(Offset.zero);
180 |
181 | double bottomPosition =
182 | MediaQuery.of(context).size.height - topLeftCornerOffset.dy;
183 | if (widget.inputOptions.inputToolbarMargin != null) {
184 | bottomPosition -= widget.inputOptions.inputToolbarMargin!.top -
185 | widget.inputOptions.inputToolbarMargin!.bottom;
186 | }
187 |
188 | _clearOverlay();
189 |
190 | _overlayEntry = OverlayEntry(
191 | builder: (BuildContext context) {
192 | return Positioned(
193 | width: renderBox.size.width,
194 | bottom: bottomPosition,
195 | child: Container(
196 | constraints: BoxConstraints(
197 | maxHeight: MediaQuery.of(context).size.height -
198 | bottomPosition -
199 | MediaQuery.of(context).padding.top -
200 | kToolbarHeight,
201 | ),
202 | decoration: BoxDecoration(
203 | color: Theme.of(context).canvasColor,
204 | border: Border(
205 | top: BorderSide(
206 | width: 0.2,
207 | color: Theme.of(context).dividerColor,
208 | ),
209 | ),
210 | ),
211 | child: Material(
212 | color: Theme.of(context).hoverColor,
213 | child: SingleChildScrollView(
214 | child: Column(
215 | children: children,
216 | ),
217 | ),
218 | ),
219 | ),
220 | );
221 | },
222 | );
223 | overlay.insert(_overlayEntry!);
224 | }
225 |
226 | void _sendMessage() {
227 | if (textController.text.isNotEmpty) {
228 | final ChatMessage message = ChatMessage(
229 | text: textController.text,
230 | user: widget.currentUser,
231 | createdAt: DateTime.now(),
232 | );
233 | widget.onSend(message);
234 | textController.text = '';
235 | if (widget.inputOptions.onTextChange != null) {
236 | widget.inputOptions.onTextChange!('');
237 | }
238 | }
239 | }
240 | }
241 |
--------------------------------------------------------------------------------
/lib/src/widgets/message_list/message_list.dart:
--------------------------------------------------------------------------------
1 | part of '../../../dash_chat_2.dart';
2 |
3 | /// @nodoc
4 | class MessageList extends StatefulWidget {
5 | const MessageList({
6 | required this.currentUser,
7 | required this.messages,
8 | this.readOnly = false,
9 | this.messageOptions = const MessageOptions(),
10 | this.messageListOptions = const MessageListOptions(),
11 | this.quickReplyOptions = const QuickReplyOptions(),
12 | this.scrollToBottomOptions = const ScrollToBottomOptions(),
13 | this.typingUsers,
14 | Key? key,
15 | }) : super(key: key);
16 |
17 | /// The current user of the chat
18 | final ChatUser currentUser;
19 |
20 | /// List of messages visible in the chat
21 | final List messages;
22 |
23 | /// Whether the chat is read only, used for safe area
24 | final bool readOnly;
25 |
26 | /// Options to customize the behaviour and design of the messages
27 | final MessageOptions messageOptions;
28 |
29 | /// Options to customize the behaviour and design of the overall list of message
30 | final MessageListOptions messageListOptions;
31 |
32 | /// Options to customize the behaviour and design of the quick replies
33 | final QuickReplyOptions quickReplyOptions;
34 |
35 | /// Options to customize the behaviour and design of the scroll-to-bottom button
36 | final ScrollToBottomOptions scrollToBottomOptions;
37 |
38 | /// List of users currently typing in the chat
39 | final List? typingUsers;
40 |
41 | @override
42 | State createState() => MessageListState();
43 | }
44 |
45 | class MessageListState extends State {
46 | bool scrollToBottomIsVisible = false;
47 | bool isLoadingMore = false;
48 | late ScrollController scrollController;
49 |
50 | @override
51 | void initState() {
52 | scrollController =
53 | widget.messageListOptions.scrollController ?? ScrollController();
54 | scrollController.addListener(() => _onScroll());
55 | super.initState();
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return GestureDetector(
61 | onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
62 | child: Stack(
63 | children: [
64 | Column(
65 | crossAxisAlignment: CrossAxisAlignment.start,
66 | children: [
67 | Expanded(
68 | child: ListView.builder(
69 | physics: widget.messageListOptions.scrollPhysics,
70 | padding: widget.readOnly ? null : EdgeInsets.zero,
71 | controller: scrollController,
72 | reverse: true,
73 | itemCount: widget.messages.length,
74 | itemBuilder: (BuildContext context, int i) {
75 | final ChatMessage? previousMessage =
76 | i < widget.messages.length - 1
77 | ? widget.messages[i + 1]
78 | : null;
79 | final ChatMessage? nextMessage =
80 | i > 0 ? widget.messages[i - 1] : null;
81 | final ChatMessage message = widget.messages[i];
82 | final bool isAfterDateSeparator = _shouldShowDateSeparator(
83 | previousMessage, message, widget.messageListOptions);
84 | bool isBeforeDateSeparator = false;
85 | if (nextMessage != null) {
86 | isBeforeDateSeparator = _shouldShowDateSeparator(
87 | message, nextMessage, widget.messageListOptions);
88 | }
89 | return Column(
90 | children: [
91 | if (isAfterDateSeparator)
92 | widget.messageListOptions.dateSeparatorBuilder != null
93 | ? widget.messageListOptions
94 | .dateSeparatorBuilder!(message.createdAt)
95 | : DefaultDateSeparator(
96 | date: message.createdAt,
97 | messageListOptions: widget.messageListOptions,
98 | ),
99 | if (widget.messageOptions.messageRowBuilder !=
100 | null) ...[
101 | widget.messageOptions.messageRowBuilder!(
102 | message,
103 | previousMessage,
104 | nextMessage,
105 | isAfterDateSeparator,
106 | isBeforeDateSeparator,
107 | ),
108 | ] else
109 | MessageRow(
110 | message: widget.messages[i],
111 | nextMessage: nextMessage,
112 | previousMessage: previousMessage,
113 | currentUser: widget.currentUser,
114 | isAfterDateSeparator: isAfterDateSeparator,
115 | isBeforeDateSeparator: isBeforeDateSeparator,
116 | messageOptions: widget.messageOptions,
117 | ),
118 | ],
119 | );
120 | },
121 | ),
122 | ),
123 | if (widget.typingUsers != null && widget.typingUsers!.isNotEmpty)
124 | ...widget.typingUsers!.map((ChatUser user) {
125 | if (widget.messageListOptions.typingBuilder != null) {
126 | return widget.messageListOptions.typingBuilder!(user);
127 | }
128 | return DefaultTypingBuilder(user: user);
129 | }),
130 | if (widget.messageListOptions.showFooterBeforeQuickReplies &&
131 | widget.messageListOptions.chatFooterBuilder != null)
132 | widget.messageListOptions.chatFooterBuilder!,
133 | if (widget.messages.isNotEmpty &&
134 | widget.messages.first.quickReplies != null &&
135 | widget.messages.first.quickReplies!.isNotEmpty &&
136 | widget.messages.first.user.id != widget.currentUser.id)
137 | QuickReplies(
138 | quickReplies: widget.messages.first.quickReplies!,
139 | quickReplyOptions: widget.quickReplyOptions,
140 | ),
141 | if (!widget.messageListOptions.showFooterBeforeQuickReplies &&
142 | widget.messageListOptions.chatFooterBuilder != null)
143 | widget.messageListOptions.chatFooterBuilder!,
144 | ],
145 | ),
146 | if (isLoadingMore)
147 | Positioned(
148 | top: 8.0,
149 | right: 0,
150 | left: 0,
151 | child: widget.messageListOptions.loadEarlierBuilder ??
152 | const Center(
153 | child: SizedBox(
154 | child: CircularProgressIndicator(),
155 | ),
156 | ),
157 | ),
158 | if (!widget.scrollToBottomOptions.disabled && scrollToBottomIsVisible)
159 | widget.scrollToBottomOptions.scrollToBottomBuilder != null
160 | ? widget.scrollToBottomOptions
161 | .scrollToBottomBuilder!(scrollController)
162 | : DefaultScrollToBottom(
163 | scrollController: scrollController,
164 | readOnly: widget.readOnly,
165 | backgroundColor: Theme.of(context).scaffoldBackgroundColor,
166 | textColor: Theme.of(context).primaryColor,
167 | ),
168 | ],
169 | ),
170 | );
171 | }
172 |
173 | /// Check if a date separator needs to be shown
174 | bool _shouldShowDateSeparator(ChatMessage? previousMessage,
175 | ChatMessage message, MessageListOptions messageListOptions) {
176 | if (!messageListOptions.showDateSeparator) {
177 | return false;
178 | }
179 | if (previousMessage == null) {
180 | // Means this is the first message
181 | return true;
182 | }
183 | switch (messageListOptions.separatorFrequency) {
184 | case SeparatorFrequency.days:
185 | final DateTime previousDate = DateTime(
186 | previousMessage.createdAt.year,
187 | previousMessage.createdAt.month,
188 | previousMessage.createdAt.day,
189 | );
190 | final DateTime messageDate = DateTime(
191 | message.createdAt.year,
192 | message.createdAt.month,
193 | message.createdAt.day,
194 | );
195 | return previousDate.difference(messageDate).inDays.abs() > 0;
196 | case SeparatorFrequency.hours:
197 | final DateTime previousDate = DateTime(
198 | previousMessage.createdAt.year,
199 | previousMessage.createdAt.month,
200 | previousMessage.createdAt.day,
201 | previousMessage.createdAt.hour,
202 | );
203 | final DateTime messageDate = DateTime(
204 | message.createdAt.year,
205 | message.createdAt.month,
206 | message.createdAt.day,
207 | message.createdAt.hour,
208 | );
209 | return previousDate.difference(messageDate).inHours.abs() > 0;
210 | default:
211 | return false;
212 | }
213 | }
214 |
215 | /// Scroll listener to trigger different actions:
216 | /// show scroll-to-bottom btn and LoadEarlier behaviour
217 | Future _onScroll() async {
218 | bool topReached =
219 | scrollController.offset >= scrollController.position.maxScrollExtent &&
220 | !scrollController.position.outOfRange;
221 | if (topReached &&
222 | widget.messageListOptions.onLoadEarlier != null &&
223 | !isLoadingMore) {
224 | setState(() {
225 | isLoadingMore = true;
226 | });
227 | showScrollToBottom();
228 | await widget.messageListOptions.onLoadEarlier!();
229 | setState(() {
230 | isLoadingMore = false;
231 | });
232 | } else if (scrollController.offset > 200) {
233 | showScrollToBottom();
234 | } else {
235 | hideScrollToBottom();
236 | }
237 | }
238 |
239 | void showScrollToBottom() {
240 | if (!scrollToBottomIsVisible) {
241 | setState(() {
242 | scrollToBottomIsVisible = true;
243 | });
244 | }
245 | }
246 |
247 | void hideScrollToBottom() {
248 | if (scrollToBottomIsVisible) {
249 | setState(() {
250 | scrollToBottomIsVisible = false;
251 | });
252 | }
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
The most complete Chat UI for flutter
4 |
5 | Easy to use, highly customizable and fully featured
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ## Features
29 |
30 | - Fully customizable components
31 | - Multi-line TextInput
32 | - Touchable links using [flutter_parsed_text](https://pub.dev/packages/flutter_parsed_text)
33 | - Markdown support using [flutter_markdown](https://pub.dev/packages/flutter_markdown)
34 | - Avatar as user's initials or profile picture
35 | - Quick Reply messages
36 | - Load earlier messages
37 | - Scroll to bottom Widget
38 | - Multiple media support (Audio support: WIP)
39 | - @ Mention users (or anything else)
40 | - Typing users
41 | - Reply to messages - WIP
42 | - Message status - WIP
43 |
44 | You need another feature? you can use the `customProperties` field of the models, it allows you to pass other data to the library that you can then use inside custom builders to implement any feature you need.
45 |
46 | Of course, if you think this feature can be useful to other people, feel free to open an issue/pull-request to discuss including it "natively" in the package.
47 |
48 | ## Basic Usage
49 |
50 | ```dart
51 | import 'package:dash_chat_2/dash_chat_2.dart';
52 | import 'package:flutter/material.dart';
53 |
54 | class Basic extends StatefulWidget {
55 | @override
56 | _BasicState createState() => _BasicState();
57 | }
58 |
59 | class _BasicState extends State {
60 | ChatUser user = ChatUser(
61 | id: '1',
62 | firstName: 'Charles',
63 | lastName: 'Leclerc',
64 | );
65 |
66 | List messages = [
67 | ChatMessage(
68 | text: 'Hey!',
69 | user: user,
70 | createdAt: DateTime.now(),
71 | ),
72 | ];
73 |
74 | @override
75 | Widget build(BuildContext context) {
76 | return Scaffold(
77 | appBar: AppBar(
78 | title: const Text('Basic example'),
79 | ),
80 | body: DashChat(
81 | currentUser: user,
82 | onSend: (ChatMessage m) {
83 | setState(() {
84 | messages.insert(0, m);
85 | });
86 | },
87 | messages: messages,
88 | ),
89 | );
90 | }
91 | }
92 | ```
93 |
94 | You can run the [example](example) project to see more complex ways of using the package
95 |
96 | ## Parameters of DashChat
97 |
98 | - ChatUser currentUser - required : Basically "you", we need to know who is the current user to put their messages to right side
99 |
100 | - Function(ChatMessage message) onSend - required : Function to call when the user sends a message, that's where you handle the logic to send the message to your backend and append the list of `messages`
101 |
102 | - List<ChatMessage > messages - required : The list of messages of the channel, you would usually not load all the messages at once but use the `onLoadEarlier` param of `MessageListOptions` to trigger a lazy loading
103 |
104 | - InputOptions inputOptions - optional : Options to customize the behaviour and design of the chat input
105 |
106 | - MessageOptions messageOptions - optional : Options to customize the behaviour and design of the messages
107 |
108 | - MessageListOptions messageListOptions - optional : Options to customize the behaviour and design of the overall list of message
109 |
110 | - QuickReplyOptions quickReplyOptions - optional : Options to customize the behaviour and design of the quick replies
111 |
112 | - ScrollToBottomOptions scrollToBottomOptions - optional : Options to customize the behaviour and design of the scroll-to-bottom button
113 |
114 | - readOnly - optional (default to false): Option to make the chat read only, it will hide the input field
115 |
116 | - List<ChatUser > typingUsers - optional : List of users currently typing in the chat
117 |
118 | ## Full documentation
119 |
120 | You can browse the full Dart documentation here: [Documentation](https://pub.dev/documentation/dash_chat_2/latest/)
121 |
122 | ## Found this project useful?
123 |
124 | If you found this project useful, then please consider giving it a ⭐️ on Github: [https://github.com/SebastienBtr/Dash-Chat-2](https://github.com/SebastienBtr/Dash-Chat-2)
125 |
126 | ## Issues and feedback
127 |
128 | If you have any suggestions for including a feature or if something doesn't work, feel free to open a Github [issue](https://github.com/SebastienBtr/Dash-Chat-2/issues) or to open a [pull request](https://github.com/SebastienBtr/Dash-Chat-2/pulls), you are more than welcome to contribute!
129 |
130 | ## Contributors
131 |
132 |
133 |
134 |
135 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | ## Credits
160 |
161 | Thanks to [Fayeed](https://github.com/fayeed) who created the v1 of this package: [https://github.com/fayeed/dash_chat](https://github.com/fayeed/dash_chat) and made that possible!
162 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | args:
5 | dependency: transitive
6 | description:
7 | name: args
8 | sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.5.0"
12 | async:
13 | dependency: transitive
14 | description:
15 | name: async
16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "2.11.0"
20 | boolean_selector:
21 | dependency: transitive
22 | description:
23 | name: boolean_selector
24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.1.1"
28 | cached_network_image:
29 | dependency: "direct main"
30 | description:
31 | name: cached_network_image
32 | sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "3.3.1"
36 | cached_network_image_platform_interface:
37 | dependency: transitive
38 | description:
39 | name: cached_network_image_platform_interface
40 | sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "4.0.0"
44 | cached_network_image_web:
45 | dependency: transitive
46 | description:
47 | name: cached_network_image_web
48 | sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.2.0"
52 | characters:
53 | dependency: transitive
54 | description:
55 | name: characters
56 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.3.0"
60 | clock:
61 | dependency: transitive
62 | description:
63 | name: clock
64 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "1.1.1"
68 | collection:
69 | dependency: transitive
70 | description:
71 | name: collection
72 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "1.18.0"
76 | crypto:
77 | dependency: transitive
78 | description:
79 | name: crypto
80 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "3.0.3"
84 | csslib:
85 | dependency: transitive
86 | description:
87 | name: csslib
88 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "1.0.0"
92 | fake_async:
93 | dependency: transitive
94 | description:
95 | name: fake_async
96 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "1.3.1"
100 | ffi:
101 | dependency: transitive
102 | description:
103 | name: ffi
104 | sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
105 | url: "https://pub.dev"
106 | source: hosted
107 | version: "2.1.2"
108 | file:
109 | dependency: transitive
110 | description:
111 | name: file
112 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
113 | url: "https://pub.dev"
114 | source: hosted
115 | version: "7.0.0"
116 | fixnum:
117 | dependency: transitive
118 | description:
119 | name: fixnum
120 | sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
121 | url: "https://pub.dev"
122 | source: hosted
123 | version: "1.1.0"
124 | flutter:
125 | dependency: "direct main"
126 | description: flutter
127 | source: sdk
128 | version: "0.0.0"
129 | flutter_cache_manager:
130 | dependency: transitive
131 | description:
132 | name: flutter_cache_manager
133 | sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544"
134 | url: "https://pub.dev"
135 | source: hosted
136 | version: "3.3.2"
137 | flutter_lints:
138 | dependency: "direct dev"
139 | description:
140 | name: flutter_lints
141 | sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
142 | url: "https://pub.dev"
143 | source: hosted
144 | version: "3.0.2"
145 | flutter_markdown:
146 | dependency: "direct main"
147 | description:
148 | name: flutter_markdown
149 | sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f"
150 | url: "https://pub.dev"
151 | source: hosted
152 | version: "0.7.1"
153 | flutter_parsed_text:
154 | dependency: "direct main"
155 | description:
156 | name: flutter_parsed_text
157 | sha256: "529cf5793b7acdf16ee0f97b158d0d4ba0bf06e7121ef180abe1a5b59e32c1e2"
158 | url: "https://pub.dev"
159 | source: hosted
160 | version: "2.2.1"
161 | flutter_test:
162 | dependency: "direct dev"
163 | description: flutter
164 | source: sdk
165 | version: "0.0.0"
166 | flutter_web_plugins:
167 | dependency: transitive
168 | description: flutter
169 | source: sdk
170 | version: "0.0.0"
171 | html:
172 | dependency: transitive
173 | description:
174 | name: html
175 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
176 | url: "https://pub.dev"
177 | source: hosted
178 | version: "0.15.4"
179 | http:
180 | dependency: transitive
181 | description:
182 | name: http
183 | sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
184 | url: "https://pub.dev"
185 | source: hosted
186 | version: "1.2.1"
187 | http_parser:
188 | dependency: transitive
189 | description:
190 | name: http_parser
191 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
192 | url: "https://pub.dev"
193 | source: hosted
194 | version: "4.0.2"
195 | intl:
196 | dependency: "direct main"
197 | description:
198 | name: intl
199 | sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
200 | url: "https://pub.dev"
201 | source: hosted
202 | version: "0.19.0"
203 | leak_tracker:
204 | dependency: transitive
205 | description:
206 | name: leak_tracker
207 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
208 | url: "https://pub.dev"
209 | source: hosted
210 | version: "10.0.0"
211 | leak_tracker_flutter_testing:
212 | dependency: transitive
213 | description:
214 | name: leak_tracker_flutter_testing
215 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
216 | url: "https://pub.dev"
217 | source: hosted
218 | version: "2.0.1"
219 | leak_tracker_testing:
220 | dependency: transitive
221 | description:
222 | name: leak_tracker_testing
223 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
224 | url: "https://pub.dev"
225 | source: hosted
226 | version: "2.0.1"
227 | lints:
228 | dependency: transitive
229 | description:
230 | name: lints
231 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
232 | url: "https://pub.dev"
233 | source: hosted
234 | version: "3.0.0"
235 | markdown:
236 | dependency: transitive
237 | description:
238 | name: markdown
239 | sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
240 | url: "https://pub.dev"
241 | source: hosted
242 | version: "7.2.2"
243 | matcher:
244 | dependency: transitive
245 | description:
246 | name: matcher
247 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
248 | url: "https://pub.dev"
249 | source: hosted
250 | version: "0.12.16+1"
251 | material_color_utilities:
252 | dependency: transitive
253 | description:
254 | name: material_color_utilities
255 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
256 | url: "https://pub.dev"
257 | source: hosted
258 | version: "0.8.0"
259 | meta:
260 | dependency: transitive
261 | description:
262 | name: meta
263 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
264 | url: "https://pub.dev"
265 | source: hosted
266 | version: "1.11.0"
267 | octo_image:
268 | dependency: transitive
269 | description:
270 | name: octo_image
271 | sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
272 | url: "https://pub.dev"
273 | source: hosted
274 | version: "2.0.0"
275 | path:
276 | dependency: transitive
277 | description:
278 | name: path
279 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
280 | url: "https://pub.dev"
281 | source: hosted
282 | version: "1.9.0"
283 | path_provider:
284 | dependency: transitive
285 | description:
286 | name: path_provider
287 | sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
288 | url: "https://pub.dev"
289 | source: hosted
290 | version: "2.1.3"
291 | path_provider_android:
292 | dependency: transitive
293 | description:
294 | name: path_provider_android
295 | sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
296 | url: "https://pub.dev"
297 | source: hosted
298 | version: "2.2.4"
299 | path_provider_foundation:
300 | dependency: transitive
301 | description:
302 | name: path_provider_foundation
303 | sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
304 | url: "https://pub.dev"
305 | source: hosted
306 | version: "2.4.0"
307 | path_provider_linux:
308 | dependency: transitive
309 | description:
310 | name: path_provider_linux
311 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
312 | url: "https://pub.dev"
313 | source: hosted
314 | version: "2.2.1"
315 | path_provider_platform_interface:
316 | dependency: transitive
317 | description:
318 | name: path_provider_platform_interface
319 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
320 | url: "https://pub.dev"
321 | source: hosted
322 | version: "2.1.2"
323 | path_provider_windows:
324 | dependency: transitive
325 | description:
326 | name: path_provider_windows
327 | sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
328 | url: "https://pub.dev"
329 | source: hosted
330 | version: "2.2.1"
331 | platform:
332 | dependency: transitive
333 | description:
334 | name: platform
335 | sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
336 | url: "https://pub.dev"
337 | source: hosted
338 | version: "3.1.4"
339 | plugin_platform_interface:
340 | dependency: transitive
341 | description:
342 | name: plugin_platform_interface
343 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
344 | url: "https://pub.dev"
345 | source: hosted
346 | version: "2.1.8"
347 | rxdart:
348 | dependency: transitive
349 | description:
350 | name: rxdart
351 | sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
352 | url: "https://pub.dev"
353 | source: hosted
354 | version: "0.27.7"
355 | sky_engine:
356 | dependency: transitive
357 | description: flutter
358 | source: sdk
359 | version: "0.0.99"
360 | source_span:
361 | dependency: transitive
362 | description:
363 | name: source_span
364 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
365 | url: "https://pub.dev"
366 | source: hosted
367 | version: "1.10.0"
368 | sprintf:
369 | dependency: transitive
370 | description:
371 | name: sprintf
372 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
373 | url: "https://pub.dev"
374 | source: hosted
375 | version: "7.0.0"
376 | sqflite:
377 | dependency: transitive
378 | description:
379 | name: sqflite
380 | sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
381 | url: "https://pub.dev"
382 | source: hosted
383 | version: "2.3.3+1"
384 | sqflite_common:
385 | dependency: transitive
386 | description:
387 | name: sqflite_common
388 | sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
389 | url: "https://pub.dev"
390 | source: hosted
391 | version: "2.5.4"
392 | stack_trace:
393 | dependency: transitive
394 | description:
395 | name: stack_trace
396 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
397 | url: "https://pub.dev"
398 | source: hosted
399 | version: "1.11.1"
400 | stream_channel:
401 | dependency: transitive
402 | description:
403 | name: stream_channel
404 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
405 | url: "https://pub.dev"
406 | source: hosted
407 | version: "2.1.2"
408 | string_scanner:
409 | dependency: transitive
410 | description:
411 | name: string_scanner
412 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
413 | url: "https://pub.dev"
414 | source: hosted
415 | version: "1.2.0"
416 | synchronized:
417 | dependency: transitive
418 | description:
419 | name: synchronized
420 | sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
421 | url: "https://pub.dev"
422 | source: hosted
423 | version: "3.1.0+1"
424 | term_glyph:
425 | dependency: transitive
426 | description:
427 | name: term_glyph
428 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
429 | url: "https://pub.dev"
430 | source: hosted
431 | version: "1.2.1"
432 | test_api:
433 | dependency: transitive
434 | description:
435 | name: test_api
436 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
437 | url: "https://pub.dev"
438 | source: hosted
439 | version: "0.6.1"
440 | typed_data:
441 | dependency: transitive
442 | description:
443 | name: typed_data
444 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
445 | url: "https://pub.dev"
446 | source: hosted
447 | version: "1.3.2"
448 | url_launcher:
449 | dependency: "direct main"
450 | description:
451 | name: url_launcher
452 | sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
453 | url: "https://pub.dev"
454 | source: hosted
455 | version: "6.2.6"
456 | url_launcher_android:
457 | dependency: transitive
458 | description:
459 | name: url_launcher_android
460 | sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
461 | url: "https://pub.dev"
462 | source: hosted
463 | version: "6.3.2"
464 | url_launcher_ios:
465 | dependency: transitive
466 | description:
467 | name: url_launcher_ios
468 | sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
469 | url: "https://pub.dev"
470 | source: hosted
471 | version: "6.3.0"
472 | url_launcher_linux:
473 | dependency: transitive
474 | description:
475 | name: url_launcher_linux
476 | sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
477 | url: "https://pub.dev"
478 | source: hosted
479 | version: "3.1.1"
480 | url_launcher_macos:
481 | dependency: transitive
482 | description:
483 | name: url_launcher_macos
484 | sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
485 | url: "https://pub.dev"
486 | source: hosted
487 | version: "3.2.0"
488 | url_launcher_platform_interface:
489 | dependency: transitive
490 | description:
491 | name: url_launcher_platform_interface
492 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
493 | url: "https://pub.dev"
494 | source: hosted
495 | version: "2.3.2"
496 | url_launcher_web:
497 | dependency: transitive
498 | description:
499 | name: url_launcher_web
500 | sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
501 | url: "https://pub.dev"
502 | source: hosted
503 | version: "2.3.1"
504 | url_launcher_windows:
505 | dependency: transitive
506 | description:
507 | name: url_launcher_windows
508 | sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
509 | url: "https://pub.dev"
510 | source: hosted
511 | version: "3.1.1"
512 | uuid:
513 | dependency: transitive
514 | description:
515 | name: uuid
516 | sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
517 | url: "https://pub.dev"
518 | source: hosted
519 | version: "4.4.0"
520 | vector_math:
521 | dependency: transitive
522 | description:
523 | name: vector_math
524 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
525 | url: "https://pub.dev"
526 | source: hosted
527 | version: "2.1.4"
528 | video_player:
529 | dependency: "direct main"
530 | description:
531 | name: video_player
532 | sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0
533 | url: "https://pub.dev"
534 | source: hosted
535 | version: "2.8.6"
536 | video_player_android:
537 | dependency: transitive
538 | description:
539 | name: video_player_android
540 | sha256: "134e1ad410d67e18a19486ed9512c72dfc6d8ffb284d0e8f2e99e903d1ba8fa3"
541 | url: "https://pub.dev"
542 | source: hosted
543 | version: "2.4.14"
544 | video_player_avfoundation:
545 | dependency: transitive
546 | description:
547 | name: video_player_avfoundation
548 | sha256: d1e9a824f2b324000dc8fb2dcb2a3285b6c1c7c487521c63306cc5b394f68a7c
549 | url: "https://pub.dev"
550 | source: hosted
551 | version: "2.6.1"
552 | video_player_platform_interface:
553 | dependency: transitive
554 | description:
555 | name: video_player_platform_interface
556 | sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6"
557 | url: "https://pub.dev"
558 | source: hosted
559 | version: "6.2.2"
560 | video_player_web:
561 | dependency: transitive
562 | description:
563 | name: video_player_web
564 | sha256: ff4d69a6614b03f055397c27a71c9d3ddea2b2a23d71b2ba0164f59ca32b8fe2
565 | url: "https://pub.dev"
566 | source: hosted
567 | version: "2.3.1"
568 | vm_service:
569 | dependency: transitive
570 | description:
571 | name: vm_service
572 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
573 | url: "https://pub.dev"
574 | source: hosted
575 | version: "13.0.0"
576 | web:
577 | dependency: transitive
578 | description:
579 | name: web
580 | sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
581 | url: "https://pub.dev"
582 | source: hosted
583 | version: "0.5.1"
584 | win32:
585 | dependency: transitive
586 | description:
587 | name: win32
588 | sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
589 | url: "https://pub.dev"
590 | source: hosted
591 | version: "5.5.0"
592 | xdg_directories:
593 | dependency: transitive
594 | description:
595 | name: xdg_directories
596 | sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
597 | url: "https://pub.dev"
598 | source: hosted
599 | version: "1.0.4"
600 | sdks:
601 | dart: ">=3.3.0 <4.0.0"
602 | flutter: ">=3.19.0"
603 |
--------------------------------------------------------------------------------