├── ios
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── 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
├── Runner.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ └── project.pbxproj
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
└── .gitignore
├── lib
├── chat_message_type.dart
├── formatter.dart
├── main.dart
├── chat_controller.dart
├── test_pls_delete_this.dart
├── bubble.dart
├── chat_screen.dart
└── chat.dart
├── 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
│ │ │ │ │ └── ximya
│ │ │ │ │ └── basic_chat_ui_implementation
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── AndroidManifest.xml
│ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ └── profile
│ │ │ └── AndroidManifest.xml
│ └── build.gradle
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
└── build.gradle
├── assets
├── images
│ └── avatar_1.png
└── icons
│ └── send.svg
├── pubspec.yaml
├── .gitignore
├── .metadata
├── test
└── widget_test.dart
├── analysis_options.yaml
├── pubspec.lock
└── README.md
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/chat_message_type.dart:
--------------------------------------------------------------------------------
1 | enum ChatMessageType {
2 | sent,
3 | received,
4 | }
5 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/assets/images/avatar_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/assets/images/avatar_1.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Xim-ya/basic_chat_ui_Implementation/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/ximya/basic_chat_ui_implementation/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ximya.basic_chat_ui_implementation
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
6 |
--------------------------------------------------------------------------------
/lib/formatter.dart:
--------------------------------------------------------------------------------
1 | import 'package:intl/intl.dart';
2 |
3 | abstract class Formatter {
4 | Formatter._();
5 |
6 | static String formatDateTime(DateTime dateTime) {
7 | final DateFormat dateFormat = DateFormat('hh:mm a');
8 | return dateFormat.format(dateTime);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: basic_chat_ui_implementation
2 | description: A new Flutter project.
3 |
4 | publish_to: 'none'
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: '>=2.19.4 <3.0.0'
10 |
11 |
12 | dependencies:
13 | flutter:
14 | sdk: flutter
15 |
16 | provider: ^6.0.5
17 | flutter_svg: ^2.0.5
18 | cupertino_icons: ^1.0.2
19 | flutter_chat_bubble: ^2.0.2
20 | intl: ^0.18.1
21 |
22 | dev_dependencies:
23 | flutter_test:
24 | sdk: flutter
25 |
26 |
27 | flutter_lints: ^2.0.0
28 |
29 | flutter:
30 | uses-material-design: true
31 |
32 | assets:
33 | - assets/icons/
34 | - assets/images/
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.2.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:basic_chat_ui_implementation/chat_controller.dart';
2 | import 'package:basic_chat_ui_implementation/chat_screen.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:provider/provider.dart';
5 |
6 | void main() {
7 | runApp(const MyApp());
8 | }
9 |
10 | class MyApp extends StatelessWidget {
11 | const MyApp({super.key});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return MaterialApp(
16 | title: 'Chat UI Guide',
17 | debugShowCheckedModeBanner: false,
18 | theme: ThemeData(
19 | primarySwatch: Colors.blue,
20 | ),
21 | home: ChangeNotifierProvider(
22 | create: (_) => ChatController(),
23 | child: const ChatScreen(),
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/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 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
17 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
18 | - platform: android
19 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
20 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
21 | - platform: ios
22 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
23 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility in the flutter_test package. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:basic_chat_ui_implementation/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/lib/chat_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:basic_chat_ui_implementation/chat.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class ChatController extends ChangeNotifier {
5 | /* Variables */
6 | List chatList = Chat.generate();
7 |
8 | /* Controllers */
9 | late final ScrollController scrollController = ScrollController();
10 | late final TextEditingController textEditingController =
11 | TextEditingController();
12 | late final FocusNode focusNode = FocusNode();
13 |
14 | /* Intents */
15 | Future onFieldSubmitted() async {
16 | if (!isTextFieldEnable) return;
17 |
18 | // 1. chat list에 첫 번째 배열 위치에 put
19 | chatList = [
20 | ...chatList,
21 | Chat.sent(message: textEditingController.text),
22 | ];
23 |
24 | // 2. 스크롤 최적화 위치
25 | // 가장 위에 스크롤 된 상태에서 채팅을 입력했을 때 최근 submit한 채팅 메세지가 보이도록
26 | // 스크롤 위치를 가장 아래 부분으로 변경
27 | scrollController.animateTo(
28 | 0,
29 | duration: const Duration(milliseconds: 300),
30 | curve: Curves.easeInOut,
31 | );
32 |
33 | textEditingController.text = '';
34 | notifyListeners();
35 | }
36 |
37 | void onFieldChanged(String term) {
38 | notifyListeners();
39 | }
40 |
41 | /* Getters */
42 | bool get isTextFieldEnable => textEditingController.text.isNotEmpty;
43 | }
44 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
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 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/assets/icons/send.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Basic Chat Ui Implementation
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | basic_chat_ui_implementation
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/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 flutter.compileSdkVersion
30 | ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "com.ximya.basic_chat_ui_implementation"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
50 | minSdkVersion flutter.minSdkVersion
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | }
55 |
56 | buildTypes {
57 | release {
58 | // TODO: Add your own signing config for the release build.
59 | // Signing with the debug keys for now, so `flutter run --release` works.
60 | signingConfig signingConfigs.debug
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | }
72 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/lib/test_pls_delete_this.dart:
--------------------------------------------------------------------------------
1 | // import 'package:basic_chat_ui_implementation/chat.dart';
2 | // import 'package:basic_chat_ui_implementation/chat_controller.dart';
3 | // import 'package:basic_chat_ui_implementation/bubble.dart';
4 | // import 'package:flutter/material.dart';
5 | // import 'package:flutter_svg/svg.dart';
6 | // import 'package:provider/provider.dart';
7 | //
8 | // class ChatScreen extends StatelessWidget {
9 | // const ChatScreen({Key? key}) : super(key: key);
10 | //
11 | // @override
12 | // Widget build(BuildContext context) {
13 | // return Scaffold(
14 | // appBar: AppBar(
15 | // title: const Text("Chat"),
16 | // backgroundColor: const Color(0xFF007AFF),
17 | // ),
18 | // body: Column(
19 | // children: [
20 | // Expanded(
21 | // child: ListView.separated(...),
22 | // ), // <- 채팅 리스트 뷰
23 | // const _BottomInputField(), // <- 하단 고정 TextField
24 | // ],
25 | // ),
26 | // );
27 | // }
28 | // }
29 | //
30 | // /// Bottom Fixed Filed
31 | // class _BottomInputField extends StatelessWidget {
32 | // const _BottomInputField({Key? key}) : super(key: key);
33 | //
34 | // @override
35 | // Widget build(BuildContext context) {
36 | // return SafeArea(
37 | // bottom: true,
38 | // child: Container(
39 | // constraints: const BoxConstraints(minHeight: 48),
40 | // width: double.infinity,
41 | // decoration: const BoxDecoration(
42 | // border: Border(
43 | // top: BorderSide(
44 | // color: Color(0xFFE5E5EA),
45 | // ),
46 | // ),
47 | // ),
48 | // child: Stack(
49 | // children: [
50 | // TextField(
51 | // focusNode: context.read().focusNode,
52 | // onChanged: context.read().onFieldChanged,
53 | // controller: context.read().textEditingController,
54 | // maxLines: null,
55 | // textAlignVertical: TextAlignVertical.top,
56 | // decoration: InputDecoration(
57 | // border: InputBorder.none,
58 | // contentPadding: const EdgeInsets.only(
59 | // right: 42,
60 | // left: 16,
61 | // top: 18,
62 | // ),
63 | // hintText: 'message',
64 | // enabledBorder: OutlineInputBorder(
65 | // borderSide: BorderSide.none,
66 | // borderRadius: BorderRadius.circular(8.0),
67 | // ),
68 | // focusedBorder: OutlineInputBorder(
69 | // borderSide: BorderSide.none,
70 | // borderRadius: BorderRadius.circular(8.0),
71 | // ),
72 | // ),
73 | // ),
74 | // // custom suffix btn
75 | // Positioned(
76 | // bottom: 0,
77 | // right: 0,
78 | // child: IconButton(
79 | // icon: SvgPicture.asset(
80 | // "assets/icons/send.svg",
81 | // colorFilter: ColorFilter.mode(
82 | // context.select(
83 | // (value) => value.isTextFieldEnable)
84 | // ? const Color(0xFF007AFF)
85 | // : const Color(0xFFBDBDC2),
86 | // BlendMode.srcIn,
87 | // ),
88 | // ),
89 | // onPressed: context.read().onFieldSubmitted,
90 | // ),
91 | // ),
92 | // ],
93 | // ),
94 | // ),
95 | // );
96 | // }
97 | // }
98 |
--------------------------------------------------------------------------------
/lib/bubble.dart:
--------------------------------------------------------------------------------
1 | library flutter_chat_bubble;
2 |
3 | import 'package:basic_chat_ui_implementation/chat.dart';
4 | import 'package:basic_chat_ui_implementation/chat_message_type.dart';
5 | import 'package:basic_chat_ui_implementation/formatter.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:flutter_chat_bubble/bubble_type.dart';
8 | import 'package:flutter_chat_bubble/clippers/chat_bubble_clipper_1.dart';
9 |
10 | class Bubble extends StatelessWidget {
11 | final EdgeInsetsGeometry? margin;
12 | final Chat chat;
13 |
14 | const Bubble({
15 | super.key,
16 | this.margin,
17 | required this.chat,
18 | });
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return Row(
23 | mainAxisAlignment: alignmentOnType,
24 | crossAxisAlignment: CrossAxisAlignment.start,
25 | children: [
26 | if (chat.type == ChatMessageType.received)
27 | const CircleAvatar(
28 | backgroundImage: AssetImage("assets/images/avatar_1.png"),
29 | ),
30 | Container(
31 | margin: margin ?? EdgeInsets.zero,
32 | child: PhysicalShape(
33 | clipper: clipperOnType,
34 | elevation: 2,
35 | color: bgColorOnType,
36 | shadowColor: Colors.grey.shade200,
37 | child: Container(
38 | constraints: BoxConstraints(
39 | maxWidth: MediaQuery.of(context).size.width * 0.8,
40 | ),
41 | padding: paddingOnType,
42 | child: Column(
43 | crossAxisAlignment: crossAlignmentOnType,
44 | children: [
45 | Text(
46 | chat.message,
47 | style: TextStyle(color: textColorOnType),
48 | ),
49 | const SizedBox(
50 | height: 8,
51 | ),
52 | Text(
53 | Formatter.formatDateTime(chat.time),
54 | style: TextStyle(color: textColorOnType, fontSize: 12),
55 | )
56 | ],
57 | ),
58 | ),
59 | ),
60 | ),
61 | ],
62 | );
63 | }
64 |
65 | Color get textColorOnType {
66 | switch (chat.type) {
67 | case ChatMessageType.sent:
68 | return Colors.white;
69 | case ChatMessageType.received:
70 | return const Color(0xFF0F0F0F);
71 | }
72 | }
73 |
74 | Color get bgColorOnType {
75 | switch (chat.type) {
76 | case ChatMessageType.received:
77 | return const Color(0xFFE7E7ED);
78 | case ChatMessageType.sent:
79 | return const Color(0xFF007AFF);
80 | }
81 | }
82 |
83 | CustomClipper get clipperOnType {
84 | switch (chat.type) {
85 | case ChatMessageType.sent:
86 | return ChatBubbleClipper1(type: BubbleType.sendBubble);
87 | case ChatMessageType.received:
88 | return ChatBubbleClipper1(type: BubbleType.receiverBubble);
89 | }
90 | }
91 |
92 | CrossAxisAlignment get crossAlignmentOnType {
93 | switch (chat.type) {
94 | case ChatMessageType.sent:
95 | return CrossAxisAlignment.end;
96 | case ChatMessageType.received:
97 | return CrossAxisAlignment.start;
98 | }
99 | }
100 |
101 | MainAxisAlignment get alignmentOnType {
102 | switch (chat.type) {
103 | case ChatMessageType.received:
104 | return MainAxisAlignment.start;
105 |
106 | case ChatMessageType.sent:
107 | return MainAxisAlignment.end;
108 | }
109 | }
110 |
111 | EdgeInsets get paddingOnType {
112 | switch (chat.type) {
113 | case ChatMessageType.sent:
114 | return const EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 24);
115 | case ChatMessageType.received:
116 | return const EdgeInsets.only(
117 | top: 10,
118 | bottom: 10,
119 | left: 24,
120 | right: 10,
121 | );
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/lib/chat_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:basic_chat_ui_implementation/chat.dart';
2 | import 'package:basic_chat_ui_implementation/chat_controller.dart';
3 | import 'package:basic_chat_ui_implementation/bubble.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_svg/svg.dart';
6 | import 'package:provider/provider.dart';
7 |
8 | class ChatScreen extends StatelessWidget {
9 | const ChatScreen({Key? key}) : super(key: key);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | resizeToAvoidBottomInset: true,
15 | appBar: AppBar(
16 | title: const Text("Ximya"),
17 | backgroundColor: const Color(0xFF007AFF),
18 | ),
19 | body: Column(
20 | children: [
21 | Expanded(
22 | child: GestureDetector(
23 | onTap: () {
24 | context.read().focusNode.unfocus();
25 | // FocusScope.of(context).unfocus();
26 | },
27 | child: Align(
28 | alignment: Alignment.topCenter,
29 | child: Selector>(
30 | selector: (context, controller) =>
31 | controller.chatList.reversed.toList(),
32 | builder: (context, chatList, child) {
33 | return ListView.separated(
34 | shrinkWrap: true,
35 | reverse: true,
36 | padding: const EdgeInsets.only(top: 12, bottom: 20) +
37 | const EdgeInsets.symmetric(horizontal: 12),
38 | separatorBuilder: (_, __) => const SizedBox(
39 | height: 12,
40 | ),
41 | controller:
42 | context.read().scrollController,
43 | itemCount: chatList.length,
44 | itemBuilder: (context, index) {
45 | return Bubble(chat: chatList[index]);
46 | },
47 | );
48 | },
49 | ),
50 | ),
51 | ),
52 | ),
53 | const _BottomInputField(),
54 | ],
55 | ),
56 | );
57 | }
58 | }
59 |
60 | /// Bottom Fixed Filed
61 | class _BottomInputField extends StatelessWidget {
62 | const _BottomInputField({Key? key}) : super(key: key);
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | return SafeArea(
67 | bottom: true,
68 | child: Container(
69 | constraints: const BoxConstraints(minHeight: 48),
70 | width: double.infinity,
71 | decoration: const BoxDecoration(
72 | border: Border(
73 | top: BorderSide(
74 | color: Color(0xFFE5E5EA),
75 | ),
76 | ),
77 | ),
78 | child: Stack(
79 | children: [
80 | TextField(
81 | focusNode: context.read().focusNode,
82 | onChanged: context.read().onFieldChanged,
83 | controller: context.read().textEditingController,
84 | maxLines: null,
85 | textAlignVertical: TextAlignVertical.top,
86 | decoration: InputDecoration(
87 | border: InputBorder.none,
88 | contentPadding: const EdgeInsets.only(
89 | right: 42,
90 | left: 16,
91 | top: 18,
92 | ),
93 | hintText: 'message',
94 | enabledBorder: OutlineInputBorder(
95 | borderSide: BorderSide.none,
96 | borderRadius: BorderRadius.circular(8.0),
97 | ),
98 | focusedBorder: OutlineInputBorder(
99 | borderSide: BorderSide.none,
100 | borderRadius: BorderRadius.circular(8.0),
101 | ),
102 | ),
103 | ),
104 | // custom suffix btn
105 | Positioned(
106 | bottom: 0,
107 | right: 0,
108 | child: IconButton(
109 | icon: SvgPicture.asset(
110 | "assets/icons/send.svg",
111 | colorFilter: ColorFilter.mode(
112 | context.select(
113 | (value) => value.isTextFieldEnable)
114 | ? const Color(0xFF007AFF)
115 | : const Color(0xFFBDBDC2),
116 | BlendMode.srcIn,
117 | ),
118 | ),
119 | onPressed: context.read().onFieldSubmitted,
120 | ),
121 | ),
122 | ],
123 | ),
124 | ),
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/lib/chat.dart:
--------------------------------------------------------------------------------
1 | import 'package:basic_chat_ui_implementation/chat_message_type.dart';
2 |
3 | class Chat {
4 | final String message;
5 | final ChatMessageType type;
6 | final DateTime time;
7 |
8 | Chat({required this.message, required this.type, required this.time});
9 |
10 | factory Chat.sent({required message}) =>
11 | Chat(message: message, type: ChatMessageType.sent, time: DateTime.now());
12 |
13 | static List generate() {
14 | return [
15 | Chat(
16 | message: "Hello!",
17 | type: ChatMessageType.sent,
18 | time: DateTime.now().subtract(const Duration(minutes: 15)),
19 | ),
20 | Chat(
21 | message: "Nice to meet you!",
22 | type: ChatMessageType.received,
23 | time: DateTime.now().subtract(const Duration(minutes: 14)),
24 | ),
25 | Chat(
26 | message: "The weather is nice today.",
27 | type: ChatMessageType.sent,
28 | time: DateTime.now().subtract(const Duration(minutes: 13)),
29 | ),
30 | Chat(
31 | message: "Yes, it's a great day to go out.",
32 | type: ChatMessageType.received,
33 | time: DateTime.now().subtract(const Duration(minutes: 12)),
34 | ),
35 | Chat(
36 | message: "Have a nice day!",
37 | type: ChatMessageType.sent,
38 | time: DateTime.now().subtract(const Duration(minutes: 11)),
39 | ),
40 | Chat(
41 | message: "What are your plans for the weekend?",
42 | type: ChatMessageType.received,
43 | time: DateTime.now().subtract(const Duration(minutes: 10)),
44 | ),
45 | Chat(
46 | message: "I'm planning to go to the beach.",
47 | type: ChatMessageType.sent,
48 | time: DateTime.now().subtract(const Duration(minutes: 9)),
49 | ),
50 | Chat(
51 | message: "That sounds fun!",
52 | type: ChatMessageType.received,
53 | time: DateTime.now().subtract(const Duration(minutes: 8)),
54 | ),
55 | Chat(
56 | message: "Do you want to come with me?",
57 | type: ChatMessageType.sent,
58 | time: DateTime.now().subtract(const Duration(minutes: 7)),
59 | ),
60 | Chat(
61 | message: "Sure, I'd love to!",
62 | type: ChatMessageType.received,
63 | time: DateTime.now().subtract(const Duration(minutes: 6)),
64 | ),
65 | Chat(
66 | message: "What time should we meet?",
67 | type: ChatMessageType.received,
68 | time: DateTime.now().subtract(const Duration(minutes: 5)),
69 | ),
70 | Chat(
71 | message: "Let's meet at 10am.",
72 | type: ChatMessageType.sent,
73 | time: DateTime.now().subtract(const Duration(minutes: 4)),
74 | ),
75 | Chat(
76 | message: "Sounds good to me!",
77 | type: ChatMessageType.received,
78 | time: DateTime.now().subtract(const Duration(minutes: 3)),
79 | ),
80 | Chat(
81 | message: "See you then!",
82 | type: ChatMessageType.sent,
83 | time: DateTime.now().subtract(const Duration(minutes: 2)),
84 | ),
85 | Chat(
86 | message: "Bye!",
87 | type: ChatMessageType.received,
88 | time: DateTime.now().subtract(const Duration(minutes: 1)),
89 | ),
90 | Chat(
91 | message: "How was your weekend?",
92 | type: ChatMessageType.received,
93 | time: DateTime.now().subtract(const Duration(minutes: 1)),
94 | ),
95 | Chat(
96 | message: "It was great! The beach was awesome.",
97 | type: ChatMessageType.sent,
98 | time: DateTime.now(),
99 | ),
100 | Chat(
101 | message: "I'm glad to hear that!",
102 | type: ChatMessageType.received,
103 | time: DateTime.now(),
104 | ),
105 | Chat(
106 | message: "We should do that again sometime.",
107 | type: ChatMessageType.sent,
108 | time: DateTime.now(),
109 | ),
110 | Chat(
111 | message: "Definitely!",
112 | type: ChatMessageType.received,
113 | time: DateTime.now(),
114 | ),
115 | ];
116 | }
117 |
118 |
119 | // static List generate() {
120 | // return [
121 | // Chat(
122 | // message: "Hello!",
123 | // type: ChatMessageType.sent,
124 | // time: DateTime.now().subtract(const Duration(minutes: 5)),
125 | // ),
126 | // Chat(
127 | // message: "Nice to meet you!",
128 | // type: ChatMessageType.received,
129 | // time: DateTime.now().subtract(const Duration(minutes: 4)),
130 | // ),
131 | // Chat(
132 | // message: "The weather is nice today.",
133 | // type: ChatMessageType.sent,
134 | // time: DateTime.now().subtract(const Duration(minutes: 3)),
135 | // ),
136 | // Chat(
137 | // message: "Yes, it's a great day to go out.",
138 | // type: ChatMessageType.received,
139 | // time: DateTime.now().subtract(const Duration(minutes: 2)),
140 | // ),
141 | // Chat(
142 | // message: "Have a nice day!",
143 | // type: ChatMessageType.sent,
144 | // time: DateTime.now().subtract(const Duration(minutes: 1)),
145 | // ),
146 | // ];
147 | // }
148 | }
149 |
--------------------------------------------------------------------------------
/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: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "2.4.1"
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 | characters:
29 | dependency: transitive
30 | description:
31 | name: characters
32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "1.3.0"
36 | clock:
37 | dependency: transitive
38 | description:
39 | name: clock
40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "1.1.1"
44 | collection:
45 | dependency: transitive
46 | description:
47 | name: collection
48 | sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.17.1"
52 | cupertino_icons:
53 | dependency: "direct main"
54 | description:
55 | name: cupertino_icons
56 | sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.0.5"
60 | fake_async:
61 | dependency: transitive
62 | description:
63 | name: fake_async
64 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "1.3.1"
68 | flutter:
69 | dependency: "direct main"
70 | description: flutter
71 | source: sdk
72 | version: "0.0.0"
73 | flutter_chat_bubble:
74 | dependency: "direct main"
75 | description:
76 | name: flutter_chat_bubble
77 | sha256: ede33df7237fea3d89464c8493bfa0bee2f94f50fd097ebd30c517c3c0d4fcf9
78 | url: "https://pub.dev"
79 | source: hosted
80 | version: "2.0.2"
81 | flutter_lints:
82 | dependency: "direct dev"
83 | description:
84 | name: flutter_lints
85 | sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
86 | url: "https://pub.dev"
87 | source: hosted
88 | version: "2.0.1"
89 | flutter_svg:
90 | dependency: "direct main"
91 | description:
92 | name: flutter_svg
93 | sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f
94 | url: "https://pub.dev"
95 | source: hosted
96 | version: "2.0.5"
97 | flutter_test:
98 | dependency: "direct dev"
99 | description: flutter
100 | source: sdk
101 | version: "0.0.0"
102 | intl:
103 | dependency: "direct main"
104 | description:
105 | name: intl
106 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
107 | url: "https://pub.dev"
108 | source: hosted
109 | version: "0.18.1"
110 | js:
111 | dependency: transitive
112 | description:
113 | name: js
114 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
115 | url: "https://pub.dev"
116 | source: hosted
117 | version: "0.6.7"
118 | lints:
119 | dependency: transitive
120 | description:
121 | name: lints
122 | sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
123 | url: "https://pub.dev"
124 | source: hosted
125 | version: "2.0.1"
126 | matcher:
127 | dependency: transitive
128 | description:
129 | name: matcher
130 | sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
131 | url: "https://pub.dev"
132 | source: hosted
133 | version: "0.12.15"
134 | material_color_utilities:
135 | dependency: transitive
136 | description:
137 | name: material_color_utilities
138 | sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
139 | url: "https://pub.dev"
140 | source: hosted
141 | version: "0.2.0"
142 | meta:
143 | dependency: transitive
144 | description:
145 | name: meta
146 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
147 | url: "https://pub.dev"
148 | source: hosted
149 | version: "1.9.1"
150 | nested:
151 | dependency: transitive
152 | description:
153 | name: nested
154 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
155 | url: "https://pub.dev"
156 | source: hosted
157 | version: "1.0.0"
158 | path:
159 | dependency: transitive
160 | description:
161 | name: path
162 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
163 | url: "https://pub.dev"
164 | source: hosted
165 | version: "1.8.3"
166 | path_parsing:
167 | dependency: transitive
168 | description:
169 | name: path_parsing
170 | sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
171 | url: "https://pub.dev"
172 | source: hosted
173 | version: "1.0.1"
174 | petitparser:
175 | dependency: transitive
176 | description:
177 | name: petitparser
178 | sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
179 | url: "https://pub.dev"
180 | source: hosted
181 | version: "5.1.0"
182 | provider:
183 | dependency: "direct main"
184 | description:
185 | name: provider
186 | sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
187 | url: "https://pub.dev"
188 | source: hosted
189 | version: "6.0.5"
190 | sky_engine:
191 | dependency: transitive
192 | description: flutter
193 | source: sdk
194 | version: "0.0.99"
195 | source_span:
196 | dependency: transitive
197 | description:
198 | name: source_span
199 | sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
200 | url: "https://pub.dev"
201 | source: hosted
202 | version: "1.9.1"
203 | stack_trace:
204 | dependency: transitive
205 | description:
206 | name: stack_trace
207 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
208 | url: "https://pub.dev"
209 | source: hosted
210 | version: "1.11.0"
211 | stream_channel:
212 | dependency: transitive
213 | description:
214 | name: stream_channel
215 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
216 | url: "https://pub.dev"
217 | source: hosted
218 | version: "2.1.1"
219 | string_scanner:
220 | dependency: transitive
221 | description:
222 | name: string_scanner
223 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
224 | url: "https://pub.dev"
225 | source: hosted
226 | version: "1.2.0"
227 | term_glyph:
228 | dependency: transitive
229 | description:
230 | name: term_glyph
231 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
232 | url: "https://pub.dev"
233 | source: hosted
234 | version: "1.2.1"
235 | test_api:
236 | dependency: transitive
237 | description:
238 | name: test_api
239 | sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
240 | url: "https://pub.dev"
241 | source: hosted
242 | version: "0.5.1"
243 | vector_graphics:
244 | dependency: transitive
245 | description:
246 | name: vector_graphics
247 | sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79
248 | url: "https://pub.dev"
249 | source: hosted
250 | version: "1.1.5"
251 | vector_graphics_codec:
252 | dependency: transitive
253 | description:
254 | name: vector_graphics_codec
255 | sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced
256 | url: "https://pub.dev"
257 | source: hosted
258 | version: "1.1.5"
259 | vector_graphics_compiler:
260 | dependency: transitive
261 | description:
262 | name: vector_graphics_compiler
263 | sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca"
264 | url: "https://pub.dev"
265 | source: hosted
266 | version: "1.1.5"
267 | vector_math:
268 | dependency: transitive
269 | description:
270 | name: vector_math
271 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
272 | url: "https://pub.dev"
273 | source: hosted
274 | version: "2.1.4"
275 | xml:
276 | dependency: transitive
277 | description:
278 | name: xml
279 | sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
280 | url: "https://pub.dev"
281 | source: hosted
282 | version: "6.2.2"
283 | sdks:
284 | dart: ">=3.0.0-0 <4.0.0"
285 | flutter: ">=3.7.0-0"
286 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to Develop a Chat App with High-Quality UI Interaction Logic
2 | 한국어 버전
3 |
4 | There are many intricate details to consider when implementing a chat UI. To create a chat app that we use dozens of times a day, we need to consider seemingly obvious details and implement a high-quality chat feature that considers the user experience (UX).
5 | This post explains how to develop a chat app that applies the UI `interaction` logic seen in representative chat apps such as WhatsApp, KakaoTalk, and Line.
6 |
7 | # Basic Structure
8 | First, let's look at the basic structure of the chat screen.
9 | ```dart
10 | Scaffold(
11 | appBar: AppBar(
12 | title: const Text("Chat"),
13 | backgroundColor: const Color(0xFF007AFF),
14 | ), // <-- App bar
15 | body: Column(
16 | children: [
17 | Expanded(
18 | child: ListView.separated(...), // <- Chat list view
19 | ),
20 | _BottomInputField(), // <- Fixed bottom TextField widget
21 | ],
22 | ),
23 | );
24 | ```
25 | Generally, the chat screen has a simple structure. It consists of an `AppBar`, `Chat ListView`, and a` TextField` fixed at the bottom.
26 |
27 | An important point here is that the chat list view and the text field must be wrapped in a `Column` widget, and the chat list view section must be wrapped in an `Expanded` widget.
28 |
29 | The `chat list view` and `input field` wrapped in a Column widget are arranged vertically, and since the `chat list view section` is wrapped in Expanded, the `input field` view is naturally fixed at the bottom. This has the advantage of not needing to fix the `input field` widget at the bottom using Stack & Positioned widgets.
30 | Please note that the examples I will continue to show are also arranged in this structure.
31 |
32 |
33 | # 1. Interaction where the input field and chat list view section respond to changes when the virtual keyboard area is detected
34 |
35 |
36 | The first chat interaction to consider is how the `input field` and `chat list` `view section` respond to changes when the `virtual keyboard` appears. It is important for the user experience that when the virtual keyboard appears, the input field and chat list view naturally follow the movement.
37 | To achieve this, you need to set the following two `properties`
38 |
39 | ### resizeToAvoidBottomInset property
40 |
41 | ```dart
42 | return Scaffold(
43 | resizeToAvoidBottomInset: true, // assign true
44 | appBar: AppBar(
45 | title: const Text("Ximya"),
46 | backgroundColor: const Color(0xFF007AFF),
47 | ),
48 | ```
49 | First, you need to set the `resizeToAvoidBottomInset` property of the Scaffold widget to `true`. When this property is set to true, the Scaffold widget automatically adjusts its size to avoid overlapping with the virtual keyboard when the `virtual keyboard` appears.
50 |
51 | ### reversed property
52 | ```dart
53 | ListView.separated(
54 | reverse: true,
55 | itemCount: chatList.length,
56 | ...
57 | )
58 | ```
59 | Secondly, you need to set the `reversed` property of the `ListView` widget to `true`. This property specifies whether to arrange the list items in reverse order. By setting reversed to true, items are arranged from bottom to top, and the size change of the virtual keyboard can be detected.
60 |
61 | > NOTE: index and Position When reversed
62 | true is set, the items in the ListView are arranged from bottom to top. As a result, the index and position of the items on the screen are reversed. This needs to be considered when manipulating the data passed to the ListView. If data manipulation is necessary, reversing the values once more before passing data to the ListView might be the solution.
63 | For example, controller.chatList.reversed.toList().
64 |
65 |
66 | # 2. Interaction when chat is added and scrolled down!
67 |
68 | When a message is added to the chat list, it should be placed at the bottom and scroll naturally. To achieve this, you need to set the `reversed` property of the ListView to `true`. By setting reversed to true, items are arranged from bottom to top. Therefore, when a message is added, the area of the ListView expands and the scroll position changes.
69 |
70 |
71 | # 3. Aligning Chat Messages to the Top
72 |
73 | So far, I've told you that you need to set the `reversed` property of the ListView widget to `true`. However, this leads to the issue of the chat list section being placed at the very bottom of the screen.
74 | ```dart
75 | Align(
76 | alignment: Alignment.topCenter,
77 | child: ListView.separated(
78 | shrinkWrap: true,
79 | reverse: true,
80 | itemCount: chatList.length,
81 | itemBuilder: (context, index) {
82 | return Bubble(chat: chatList[index]);
83 | },
84 | );
85 | ),
86 | ```
87 | Since setting the reversed property to true places the chat list section at the very bottom of the screen, you need to make some modifications to make the chat messages appear at the top of the screen. Wrap the ListView widget with `Align` and set the alignment property to Alignment.topCenter to place it at the top. Also, you need to set the `shrinkWrap: true` property on the ListView. This way, the ListView adjusts its size to fit its internal content and is placed at the top under the influence of the Alignment widget.
88 |
89 |
90 |
91 | # 4. Optimizing Scroll Position after Sending Messages
92 |
93 | When a chat message is sent, the scroll position should change to the very bottom, regardless of where the current scroll position is. To achieve this, you can control the scrolling behavior of the ListView using a `ScrollController`.
94 |
95 | ```dart
96 | final scrollController = ScrollController()
97 |
98 | ...
99 |
100 | ListView.separated(
101 | shrinkWrap: true,
102 | reverse: true,
103 | controller: scrollController
104 | itemCount: chatList.length,
105 | itemBuilder: (context, index) {
106 | return Bubble(chat: chatList[index]);
107 | },
108 | );
109 | ```
110 |
111 | First, initialize a ScrollController variable. Then, pass this variable to the controller property of the ListView. Now you can control the scrolling behavior of the ListView.
112 |
113 | ```dart
114 | Future onFieldSubmitted() async {
115 | addMessage();
116 |
117 | // Move the scroll position to the bottom
118 | scrollController.animateTo(
119 | 0,
120 | duration: const Duration(milliseconds: 300),
121 | curve: Curves.easeInOut,
122 | );
123 |
124 | textEditingController.text = '';
125 | }
126 | ```
127 |
128 | Then, apply the `scrollController.animatedTo` event to the method that occurs when a chat is added to add an animation that scrolls to the very bottom. The reason we passed an offset value of `0` to the animatedTo method is because, with listview.buidler set to `reversed:true`, a position of `0` essentially means the very bottom of the list.
129 |
130 |
131 | # 5. Dismissing the Virtual Keyboard on Chat Area Click
132 | Lastly, in a typical chat app, there is an `interaction where the virtual keyboard hides down` when the general chat list area is tapped while the virtual keyboard is up. To implement this, you just need to add a simple piece of code.
133 | ```dart
134 | Expanded(
135 | child: GestureDetector(
136 | onTap: () {
137 | FocusScope.of(context).unfocus(); // <-- Hide virtual keyboard
138 | },
139 | child: Align(
140 | alignment: Alignment.topCenter,
141 | child: Selector>(
142 | selector: (context, controller) =>
143 | controller.chatList.reversed.toList(),
144 | builder: (context, chatList, child) {
145 | return ListView.separated(
146 | shrinkWrap: true,
147 | reverse: true,
148 | padding: const EdgeInsets.only(top: 12, bottom: 20) +
149 | const EdgeInsets.symmetric(horizontal: 12),
150 | separatorBuilder: (_, __) => const SizedBox(
151 | height: 12,
152 | ),
153 | controller:
154 | context.read().scrollController,
155 | itemCount: chatList.length,
156 | itemBuilder: (context, index) {
157 | return Bubble(chat: chatList[index]);
158 | },
159 | );
160 | },
161 | ),
162 | ),
163 | ),
164 | ),
165 | ```
166 | Wrap the chat list section with a `GestureDetector` widget and pass the `FocusScope.of(context).unfocus()` event to the onTap function.
167 | ```dart
168 | // 1. Initialization
169 | final focusNode = FocusNode();
170 |
171 | // 2. Passing the focusNode object
172 | TextField(
173 | focusNode : focusNode,
174 | ...
175 | ),
176 |
177 | // 3. When the chat section is tapped
178 | onChatListSectinoTapped() {
179 | focusNode.unfocus()
180 | ```
181 |
182 | Another way is to use a FocusNode object to hide the virtual keyboard. Initialize a FocusNode object and set the focusNode attribute in the text field. Then, when the chat list section is tapped, call `focusNode.unfocus()` to hide the virtual keyboard.
183 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | alwaysOutOfDate = 1;
175 | buildActionMask = 2147483647;
176 | files = (
177 | );
178 | inputPaths = (
179 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
180 | );
181 | name = "Thin Binary";
182 | outputPaths = (
183 | );
184 | runOnlyForDeploymentPostprocessing = 0;
185 | shellPath = /bin/sh;
186 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
187 | };
188 | 9740EEB61CF901F6004384FC /* Run Script */ = {
189 | isa = PBXShellScriptBuildPhase;
190 | alwaysOutOfDate = 1;
191 | buildActionMask = 2147483647;
192 | files = (
193 | );
194 | inputPaths = (
195 | );
196 | name = "Run Script";
197 | outputPaths = (
198 | );
199 | runOnlyForDeploymentPostprocessing = 0;
200 | shellPath = /bin/sh;
201 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
202 | };
203 | /* End PBXShellScriptBuildPhase section */
204 |
205 | /* Begin PBXSourcesBuildPhase section */
206 | 97C146EA1CF9000F007C117D /* Sources */ = {
207 | isa = PBXSourcesBuildPhase;
208 | buildActionMask = 2147483647;
209 | files = (
210 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
211 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
212 | );
213 | runOnlyForDeploymentPostprocessing = 0;
214 | };
215 | /* End PBXSourcesBuildPhase section */
216 |
217 | /* Begin PBXVariantGroup section */
218 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
219 | isa = PBXVariantGroup;
220 | children = (
221 | 97C146FB1CF9000F007C117D /* Base */,
222 | );
223 | name = Main.storyboard;
224 | sourceTree = "";
225 | };
226 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
227 | isa = PBXVariantGroup;
228 | children = (
229 | 97C147001CF9000F007C117D /* Base */,
230 | );
231 | name = LaunchScreen.storyboard;
232 | sourceTree = "";
233 | };
234 | /* End PBXVariantGroup section */
235 |
236 | /* Begin XCBuildConfiguration section */
237 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
238 | isa = XCBuildConfiguration;
239 | buildSettings = {
240 | ALWAYS_SEARCH_USER_PATHS = NO;
241 | CLANG_ANALYZER_NONNULL = YES;
242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
243 | CLANG_CXX_LIBRARY = "libc++";
244 | CLANG_ENABLE_MODULES = YES;
245 | CLANG_ENABLE_OBJC_ARC = YES;
246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
247 | CLANG_WARN_BOOL_CONVERSION = YES;
248 | CLANG_WARN_COMMA = YES;
249 | CLANG_WARN_CONSTANT_CONVERSION = YES;
250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
252 | CLANG_WARN_EMPTY_BODY = YES;
253 | CLANG_WARN_ENUM_CONVERSION = YES;
254 | CLANG_WARN_INFINITE_RECURSION = YES;
255 | CLANG_WARN_INT_CONVERSION = YES;
256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
261 | CLANG_WARN_STRICT_PROTOTYPES = YES;
262 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
263 | CLANG_WARN_UNREACHABLE_CODE = YES;
264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
266 | COPY_PHASE_STRIP = NO;
267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
268 | ENABLE_NS_ASSERTIONS = NO;
269 | ENABLE_STRICT_OBJC_MSGSEND = YES;
270 | GCC_C_LANGUAGE_STANDARD = gnu99;
271 | GCC_NO_COMMON_BLOCKS = YES;
272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
274 | GCC_WARN_UNDECLARED_SELECTOR = YES;
275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
276 | GCC_WARN_UNUSED_FUNCTION = YES;
277 | GCC_WARN_UNUSED_VARIABLE = YES;
278 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
279 | MTL_ENABLE_DEBUG_INFO = NO;
280 | SDKROOT = iphoneos;
281 | SUPPORTED_PLATFORMS = iphoneos;
282 | TARGETED_DEVICE_FAMILY = "1,2";
283 | VALIDATE_PRODUCT = YES;
284 | };
285 | name = Profile;
286 | };
287 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
288 | isa = XCBuildConfiguration;
289 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
290 | buildSettings = {
291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
292 | CLANG_ENABLE_MODULES = YES;
293 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
294 | DEVELOPMENT_TEAM = YD8Z9K9UT3;
295 | ENABLE_BITCODE = NO;
296 | INFOPLIST_FILE = Runner/Info.plist;
297 | LD_RUNPATH_SEARCH_PATHS = (
298 | "$(inherited)",
299 | "@executable_path/Frameworks",
300 | );
301 | PRODUCT_BUNDLE_IDENTIFIER = com.ximya.basicChatUiImplementation;
302 | PRODUCT_NAME = "$(TARGET_NAME)";
303 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
304 | SWIFT_VERSION = 5.0;
305 | VERSIONING_SYSTEM = "apple-generic";
306 | };
307 | name = Profile;
308 | };
309 | 97C147031CF9000F007C117D /* Debug */ = {
310 | isa = XCBuildConfiguration;
311 | buildSettings = {
312 | ALWAYS_SEARCH_USER_PATHS = NO;
313 | CLANG_ANALYZER_NONNULL = YES;
314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
315 | CLANG_CXX_LIBRARY = "libc++";
316 | CLANG_ENABLE_MODULES = YES;
317 | CLANG_ENABLE_OBJC_ARC = YES;
318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
319 | CLANG_WARN_BOOL_CONVERSION = YES;
320 | CLANG_WARN_COMMA = YES;
321 | CLANG_WARN_CONSTANT_CONVERSION = YES;
322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
324 | CLANG_WARN_EMPTY_BODY = YES;
325 | CLANG_WARN_ENUM_CONVERSION = YES;
326 | CLANG_WARN_INFINITE_RECURSION = YES;
327 | CLANG_WARN_INT_CONVERSION = YES;
328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
332 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
333 | CLANG_WARN_STRICT_PROTOTYPES = YES;
334 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
335 | CLANG_WARN_UNREACHABLE_CODE = YES;
336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
338 | COPY_PHASE_STRIP = NO;
339 | DEBUG_INFORMATION_FORMAT = dwarf;
340 | ENABLE_STRICT_OBJC_MSGSEND = YES;
341 | ENABLE_TESTABILITY = YES;
342 | GCC_C_LANGUAGE_STANDARD = gnu99;
343 | GCC_DYNAMIC_NO_PIC = NO;
344 | GCC_NO_COMMON_BLOCKS = YES;
345 | GCC_OPTIMIZATION_LEVEL = 0;
346 | GCC_PREPROCESSOR_DEFINITIONS = (
347 | "DEBUG=1",
348 | "$(inherited)",
349 | );
350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
352 | GCC_WARN_UNDECLARED_SELECTOR = YES;
353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
354 | GCC_WARN_UNUSED_FUNCTION = YES;
355 | GCC_WARN_UNUSED_VARIABLE = YES;
356 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
357 | MTL_ENABLE_DEBUG_INFO = YES;
358 | ONLY_ACTIVE_ARCH = YES;
359 | SDKROOT = iphoneos;
360 | TARGETED_DEVICE_FAMILY = "1,2";
361 | };
362 | name = Debug;
363 | };
364 | 97C147041CF9000F007C117D /* Release */ = {
365 | isa = XCBuildConfiguration;
366 | buildSettings = {
367 | ALWAYS_SEARCH_USER_PATHS = NO;
368 | CLANG_ANALYZER_NONNULL = YES;
369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
370 | CLANG_CXX_LIBRARY = "libc++";
371 | CLANG_ENABLE_MODULES = YES;
372 | CLANG_ENABLE_OBJC_ARC = YES;
373 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
374 | CLANG_WARN_BOOL_CONVERSION = YES;
375 | CLANG_WARN_COMMA = YES;
376 | CLANG_WARN_CONSTANT_CONVERSION = YES;
377 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
379 | CLANG_WARN_EMPTY_BODY = YES;
380 | CLANG_WARN_ENUM_CONVERSION = YES;
381 | CLANG_WARN_INFINITE_RECURSION = YES;
382 | CLANG_WARN_INT_CONVERSION = YES;
383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
388 | CLANG_WARN_STRICT_PROTOTYPES = YES;
389 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
390 | CLANG_WARN_UNREACHABLE_CODE = YES;
391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
392 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
393 | COPY_PHASE_STRIP = NO;
394 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
395 | ENABLE_NS_ASSERTIONS = NO;
396 | ENABLE_STRICT_OBJC_MSGSEND = YES;
397 | GCC_C_LANGUAGE_STANDARD = gnu99;
398 | GCC_NO_COMMON_BLOCKS = YES;
399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
401 | GCC_WARN_UNDECLARED_SELECTOR = YES;
402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
403 | GCC_WARN_UNUSED_FUNCTION = YES;
404 | GCC_WARN_UNUSED_VARIABLE = YES;
405 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
406 | MTL_ENABLE_DEBUG_INFO = NO;
407 | SDKROOT = iphoneos;
408 | SUPPORTED_PLATFORMS = iphoneos;
409 | SWIFT_COMPILATION_MODE = wholemodule;
410 | SWIFT_OPTIMIZATION_LEVEL = "-O";
411 | TARGETED_DEVICE_FAMILY = "1,2";
412 | VALIDATE_PRODUCT = YES;
413 | };
414 | name = Release;
415 | };
416 | 97C147061CF9000F007C117D /* Debug */ = {
417 | isa = XCBuildConfiguration;
418 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
419 | buildSettings = {
420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
421 | CLANG_ENABLE_MODULES = YES;
422 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
423 | DEVELOPMENT_TEAM = YD8Z9K9UT3;
424 | ENABLE_BITCODE = NO;
425 | INFOPLIST_FILE = Runner/Info.plist;
426 | LD_RUNPATH_SEARCH_PATHS = (
427 | "$(inherited)",
428 | "@executable_path/Frameworks",
429 | );
430 | PRODUCT_BUNDLE_IDENTIFIER = com.ximya.basicChatUiImplementation;
431 | PRODUCT_NAME = "$(TARGET_NAME)";
432 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
433 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
434 | SWIFT_VERSION = 5.0;
435 | VERSIONING_SYSTEM = "apple-generic";
436 | };
437 | name = Debug;
438 | };
439 | 97C147071CF9000F007C117D /* Release */ = {
440 | isa = XCBuildConfiguration;
441 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
442 | buildSettings = {
443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
444 | CLANG_ENABLE_MODULES = YES;
445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
446 | DEVELOPMENT_TEAM = YD8Z9K9UT3;
447 | ENABLE_BITCODE = NO;
448 | INFOPLIST_FILE = Runner/Info.plist;
449 | LD_RUNPATH_SEARCH_PATHS = (
450 | "$(inherited)",
451 | "@executable_path/Frameworks",
452 | );
453 | PRODUCT_BUNDLE_IDENTIFIER = com.ximya.basicChatUiImplementation;
454 | PRODUCT_NAME = "$(TARGET_NAME)";
455 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
456 | SWIFT_VERSION = 5.0;
457 | VERSIONING_SYSTEM = "apple-generic";
458 | };
459 | name = Release;
460 | };
461 | /* End XCBuildConfiguration section */
462 |
463 | /* Begin XCConfigurationList section */
464 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
465 | isa = XCConfigurationList;
466 | buildConfigurations = (
467 | 97C147031CF9000F007C117D /* Debug */,
468 | 97C147041CF9000F007C117D /* Release */,
469 | 249021D3217E4FDB00AE95B9 /* Profile */,
470 | );
471 | defaultConfigurationIsVisible = 0;
472 | defaultConfigurationName = Release;
473 | };
474 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
475 | isa = XCConfigurationList;
476 | buildConfigurations = (
477 | 97C147061CF9000F007C117D /* Debug */,
478 | 97C147071CF9000F007C117D /* Release */,
479 | 249021D4217E4FDB00AE95B9 /* Profile */,
480 | );
481 | defaultConfigurationIsVisible = 0;
482 | defaultConfigurationName = Release;
483 | };
484 | /* End XCConfigurationList section */
485 | };
486 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
487 | }
488 |
--------------------------------------------------------------------------------