├── 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 | --------------------------------------------------------------------------------