fix: feedback

This commit is contained in:
mike doornenbal 2024-06-05 16:53:42 +02:00 committed by Freek van de Ven
parent 5464766747
commit 1141aea83c
38 changed files with 702 additions and 625 deletions

View file

@ -1,3 +1,10 @@
## 3.0.0
- Add theming
- add validator for group name
- fix spamming buttons
- fix user list flickering on the group creation screen
## 2.0.0 ## 2.0.0
- Add a serviceBuilder to the userstory configuration - Add a serviceBuilder to the userstory configuration

View file

@ -1,28 +1,9 @@
# This file configures the analyzer, which statically analyzes Dart code to include: package:flutter_iconica_analysis/analysis_options.yaml
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps, # Possible to overwrite the rules from the package
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml analyzer:
exclude:
linter: linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules: rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import "package:flutter/material.dart";
import 'package:flutter_chat/flutter_chat.dart'; import "package:flutter_chat/flutter_chat.dart";
void main(List<String> args) async { void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -11,25 +11,22 @@ class App extends StatelessWidget {
const App({super.key}); const App({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => const MaterialApp(
return const MaterialApp( home: Home(),
home: Home(), );
);
}
} }
class Home extends StatelessWidget { class Home extends StatelessWidget {
const Home({super.key}); const Home({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) => Center(
return Center( child: chatNavigatorUserStory(
child: chatNavigatorUserStory(context, context,
configuration: ChatUserStoryConfiguration( configuration: ChatUserStoryConfiguration(
chatService: LocalChatService(), chatService: LocalChatService(),
chatOptionsBuilder: (ctx) => ChatOptions( chatOptionsBuilder: (ctx) => const ChatOptions(),
noChatsPlaceholderBuilder: (translations) => ),
Text(translations.noUsersFound), ),
)))); );
}
} }

View file

@ -16,11 +16,23 @@ dependencies:
path: ../ path: ../
flutter_chat_firebase: flutter_chat_firebase:
path: ../../flutter_chat_firebase path: ../../flutter_chat_firebase
dependency_overrides:
flutter_chat:
path: ../../flutter_chat
flutter_chat_interface:
path: ../../flutter_chat_interface
flutter_chat_local:
path: ../../flutter_chat_local
flutter_chat_view:
path: ../../flutter_chat_view
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true

View file

@ -5,10 +5,10 @@
// gestures. You can also use WidgetTester to find child widgets in the widget // gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct. // tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter_test/flutter_test.dart'; import "package:flutter_test/flutter_test.dart";
void main() { void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async { testWidgets("Counter increments smoke test", (WidgetTester tester) async {
expect(true, true); expect(true, true);
}); });
} }

View file

@ -284,7 +284,7 @@ Widget _newGroupChatOverviewScreenRoute(
), ),
); );
if (context.mounted) { if (context.mounted) {
await Navigator.of(context).push( await Navigator.of(context).pushReplacement(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => _chatDetailScreenRoute( builder: (context) => _chatDetailScreenRoute(
configuration, configuration,

View file

@ -14,6 +14,7 @@ List<GoRoute> getChatStoryRoutes(
GoRoute( GoRoute(
path: ChatUserStoryRoutes.chatScreen, path: ChatUserStoryRoutes.chatScreen,
pageBuilder: (context, state) { pageBuilder: (context, state) {
var theme = Theme.of(context);
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var chatScreen = ChatScreen( var chatScreen = ChatScreen(
@ -47,6 +48,7 @@ List<GoRoute> getChatStoryRoutes(
chatScreen, chatScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: chatScreen, body: chatScreen,
), ),
); );
@ -58,6 +60,8 @@ List<GoRoute> getChatStoryRoutes(
var chatId = state.pathParameters['id']; var chatId = state.pathParameters['id'];
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var theme = Theme.of(context);
var chatDetailScreen = ChatDetailScreen( var chatDetailScreen = ChatDetailScreen(
chatTitleBuilder: configuration.chatTitleBuilder, chatTitleBuilder: configuration.chatTitleBuilder,
usernameBuilder: configuration.usernameBuilder, usernameBuilder: configuration.usernameBuilder,
@ -118,6 +122,7 @@ List<GoRoute> getChatStoryRoutes(
chatDetailScreen, chatDetailScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: chatDetailScreen, body: chatDetailScreen,
), ),
); );
@ -128,6 +133,8 @@ List<GoRoute> getChatStoryRoutes(
pageBuilder: (context, state) { pageBuilder: (context, state) {
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var theme = Theme.of(context);
var newChatScreen = NewChatScreen( var newChatScreen = NewChatScreen(
options: configuration.chatOptionsBuilder(context), options: configuration.chatOptionsBuilder(context),
translations: configuration.translationsBuilder?.call(context) ?? translations: configuration.translationsBuilder?.call(context) ??
@ -165,6 +172,7 @@ List<GoRoute> getChatStoryRoutes(
newChatScreen, newChatScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: newChatScreen, body: newChatScreen,
), ),
); );
@ -175,6 +183,8 @@ List<GoRoute> getChatStoryRoutes(
pageBuilder: (context, state) { pageBuilder: (context, state) {
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var theme = Theme.of(context);
var newGroupChatScreen = NewGroupChatScreen( var newGroupChatScreen = NewGroupChatScreen(
options: configuration.chatOptionsBuilder(context), options: configuration.chatOptionsBuilder(context),
translations: configuration.translationsBuilder?.call(context) ?? translations: configuration.translationsBuilder?.call(context) ??
@ -193,6 +203,7 @@ List<GoRoute> getChatStoryRoutes(
newGroupChatScreen, newGroupChatScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: newGroupChatScreen, body: newGroupChatScreen,
), ),
); );
@ -204,6 +215,8 @@ List<GoRoute> getChatStoryRoutes(
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var users = state.extra! as List<ChatUserModel>; var users = state.extra! as List<ChatUserModel>;
var theme = Theme.of(context);
var newGroupChatOverviewScreen = NewGroupChatOverviewScreen( var newGroupChatOverviewScreen = NewGroupChatOverviewScreen(
options: configuration.chatOptionsBuilder(context), options: configuration.chatOptionsBuilder(context),
translations: configuration.translationsBuilder?.call(context) ?? translations: configuration.translationsBuilder?.call(context) ??
@ -223,7 +236,7 @@ List<GoRoute> getChatStoryRoutes(
), ),
); );
if (context.mounted) { if (context.mounted) {
await context.push( context.go(
ChatUserStoryRoutes.chatDetailViewPath(chat.id ?? ''), ChatUserStoryRoutes.chatDetailViewPath(chat.id ?? ''),
); );
} }
@ -237,6 +250,7 @@ List<GoRoute> getChatStoryRoutes(
newGroupChatOverviewScreen, newGroupChatOverviewScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: newGroupChatOverviewScreen, body: newGroupChatOverviewScreen,
), ),
); );
@ -250,6 +264,8 @@ List<GoRoute> getChatStoryRoutes(
var id = userId == 'null' ? null : userId; var id = userId == 'null' ? null : userId;
var service = configuration.chatServiceBuilder?.call(context) ?? var service = configuration.chatServiceBuilder?.call(context) ??
configuration.chatService; configuration.chatService;
var theme = Theme.of(context);
var profileScreen = ChatProfileScreen( var profileScreen = ChatProfileScreen(
translations: configuration.translationsBuilder?.call(context) ?? translations: configuration.translationsBuilder?.call(context) ??
configuration.translations, configuration.translations,
@ -274,6 +290,7 @@ List<GoRoute> getChatStoryRoutes(
profileScreen, profileScreen,
) ?? ) ??
Scaffold( Scaffold(
backgroundColor: theme.colorScheme.surface,
body: profileScreen, body: profileScreen,
), ),
); );

View file

@ -4,7 +4,7 @@
name: flutter_chat name: flutter_chat
description: A new Flutter package project. description: A new Flutter package project.
version: 2.0.0 version: 3.0.0
publish_to: none publish_to: none
@ -20,23 +20,23 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_view path: packages/flutter_chat_view
ref: 2.0.0 ref: 3.0.0
flutter_chat_interface: flutter_chat_interface:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_interface path: packages/flutter_chat_interface
ref: 2.0.0 ref: 3.0.0
flutter_chat_local: flutter_chat_local:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_local path: packages/flutter_chat_local
ref: 2.0.0 ref: 3.0.0
uuid: ^4.3.3 uuid: ^4.3.3
dev_dependencies: dev_dependencies:
flutter_iconica_analysis: flutter_iconica_analysis:
git: git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0 ref: 7.0.0
flutter: flutter:

View file

@ -4,7 +4,7 @@
name: flutter_chat_firebase name: flutter_chat_firebase
description: A new Flutter package project. description: A new Flutter package project.
version: 2.0.0 version: 3.0.0
publish_to: none publish_to: none
environment: environment:
@ -23,12 +23,12 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_interface path: packages/flutter_chat_interface
ref: 2.0.0 ref: 3.0.0
dev_dependencies: dev_dependencies:
flutter_iconica_analysis: flutter_iconica_analysis:
git: git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0 ref: 7.0.0
flutter: flutter:

View file

@ -4,6 +4,6 @@
/// ///
library flutter_chat_interface; library flutter_chat_interface;
export 'package:flutter_chat_interface/src/chat_data_provider.dart'; export "package:flutter_chat_interface/src/chat_data_provider.dart";
export 'package:flutter_chat_interface/src/model/model.dart'; export "package:flutter_chat_interface/src/model/model.dart";
export 'package:flutter_chat_interface/src/service/service.dart'; export "package:flutter_chat_interface/src/service/service.dart";

View file

@ -2,8 +2,8 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
import 'package:flutter_data_interface/flutter_data_interface.dart'; import "package:flutter_data_interface/flutter_data_interface.dart";
class ChatDataProvider extends DataInterface { class ChatDataProvider extends DataInterface {
ChatDataProvider({ ChatDataProvider({

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class ChatModelInterface { abstract class ChatModelInterface {
ChatModelInterface copyWith(); ChatModelInterface copyWith();

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
/// An abstract class defining the interface for an image message in a chat. /// An abstract class defining the interface for an image message in a chat.
abstract class ChatImageMessageModelInterface extends ChatMessageModel { abstract class ChatImageMessageModelInterface extends ChatMessageModel {

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/src/model/chat_user.dart'; import "package:flutter_chat_interface/src/model/chat_user.dart";
abstract class ChatMessageModelInterface { abstract class ChatMessageModelInterface {
ChatUserModel get sender; ChatUserModel get sender;

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class ChatTextMessageModelInterface extends ChatMessageModel { abstract class ChatTextMessageModelInterface extends ChatMessageModel {
ChatTextMessageModelInterface({ ChatTextMessageModelInterface({

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import "package:flutter/material.dart";
abstract class ChatUserModelInterface { abstract class ChatUserModelInterface {
String? get id; String? get id;
@ -49,17 +49,17 @@ class ChatUserModel implements ChatUserModelInterface {
@override @override
String? get fullName { String? get fullName {
var fullName = ''; var fullName = "";
if (firstName != null && lastName != null) { if (firstName != null && lastName != null) {
fullName += '$firstName $lastName'; fullName += "$firstName $lastName";
} else if (firstName != null) { } else if (firstName != null) {
fullName += firstName!; fullName += firstName!;
} else if (lastName != null) { } else if (lastName != null) {
fullName += lastName!; fullName += lastName!;
} }
return fullName == '' ? null : fullName; return fullName == "" ? null : fullName;
} }
@override @override

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class GroupChatModelInterface extends ChatModel { abstract class GroupChatModelInterface extends ChatModel {
GroupChatModelInterface({ GroupChatModelInterface({

View file

@ -1,7 +1,7 @@
export 'chat.dart'; export "chat.dart";
export 'chat_image_message.dart'; export "chat_image_message.dart";
export 'chat_message.dart'; export "chat_message.dart";
export 'chat_text_message.dart'; export "chat_text_message.dart";
export 'chat_user.dart'; export "chat_user.dart";
export 'group_chat.dart'; export "group_chat.dart";
export 'personal_chat.dart'; export "personal_chat.dart";

View file

@ -3,7 +3,7 @@
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class PersonalChatModelInterface extends ChatModel { abstract class PersonalChatModelInterface extends ChatModel {
PersonalChatModelInterface({ PersonalChatModelInterface({

View file

@ -1,6 +1,6 @@
import 'dart:typed_data'; import "dart:typed_data";
import 'package:flutter/material.dart'; import "package:flutter/material.dart";
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
/// An abstract class defining the interface for a chat detail service. /// An abstract class defining the interface for a chat detail service.
abstract class ChatDetailService with ChangeNotifier { abstract class ChatDetailService with ChangeNotifier {

View file

@ -1,4 +1,4 @@
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class ChatOverviewService { abstract class ChatOverviewService {
/// Retrieves a stream of chats. /// Retrieves a stream of chats.

View file

@ -1,5 +1,5 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
class ChatService { class ChatService {
final ChatUserService chatUserService; final ChatUserService chatUserService;

View file

@ -1,4 +1,4 @@
export 'chat_detail_service.dart'; export "chat_detail_service.dart";
export 'chat_overview_service.dart'; export "chat_overview_service.dart";
export 'chat_service.dart'; export "chat_service.dart";
export 'user_service.dart'; export "user_service.dart";

View file

@ -1,4 +1,4 @@
import 'package:flutter_chat_interface/flutter_chat_interface.dart'; import "package:flutter_chat_interface/flutter_chat_interface.dart";
abstract class ChatUserService { abstract class ChatUserService {
/// Retrieves a user based on the ID. /// Retrieves a user based on the ID.

View file

@ -4,7 +4,7 @@
name: flutter_chat_interface name: flutter_chat_interface
description: A new Flutter package project. description: A new Flutter package project.
version: 2.0.0 version: 3.0.0
publish_to: none publish_to: none
environment: environment:
@ -23,6 +23,6 @@ dev_dependencies:
flutter_iconica_analysis: flutter_iconica_analysis:
git: git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0 ref: 7.0.0
flutter: flutter:

View file

@ -1,6 +1,6 @@
name: flutter_chat_local name: flutter_chat_local
description: "A new Flutter package project." description: "A new Flutter package project."
version: 2.0.0 version: 3.0.0
publish_to: none publish_to: none
environment: environment:
@ -14,7 +14,7 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_interface path: packages/flutter_chat_interface
ref: 2.0.0 ref: 3.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -22,5 +22,5 @@ dev_dependencies:
flutter_iconica_analysis: flutter_iconica_analysis:
git: git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0 ref: 7.0.0
flutter: flutter:

View file

@ -73,7 +73,7 @@ class _ChatBottomState extends State<ChatBottom> {
IconButton( IconButton(
onPressed: widget.onPressSelectImage, onPressed: widget.onPressSelectImage,
icon: Icon( icon: Icon(
Icons.image, Icons.image_outlined,
color: widget.iconColor, color: widget.iconColor,
), ),
), ),
@ -105,6 +105,7 @@ class _ChatBottomState extends State<ChatBottom> {
], ],
), ),
widget.translations, widget.translations,
context,
), ),
), ),
); );

View file

@ -54,7 +54,7 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
widget.message.timestamp.minute == widget.message.timestamp.minute ==
widget.previousMessage?.timestamp.minute; widget.previousMessage?.timestamp.minute;
var hasHeader = isNewDate || isSameSender; var hasHeader = isNewDate || isSameSender;
var theme = Theme.of(context);
return Padding( return Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: isNewDate || isSameSender ? 25.0 : 0, top: isNewDate || isSameSender ? 25.0 : 0,
@ -160,10 +160,7 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
) )
.split(' ') .split(' ')
.last, .last,
style: const TextStyle( style: theme.textTheme.bodySmall,
fontSize: 12,
color: Color(0xFFBBBBBB),
),
textAlign: TextAlign.end, textAlign: TextAlign.end,
), ),
], ],

View file

@ -30,84 +30,83 @@ class ChatRow extends StatelessWidget {
final Widget? avatar; final Widget? avatar;
@override @override
Widget build(BuildContext context) => Row( Widget build(BuildContext context) {
crossAxisAlignment: CrossAxisAlignment.start, var theme = Theme.of(context);
children: [ return Row(
Padding( crossAxisAlignment: CrossAxisAlignment.start,
padding: const EdgeInsets.only(left: 10.0), children: [
child: avatar, Padding(
), padding: const EdgeInsets.only(left: 10.0),
Expanded( child: avatar,
child: Padding( ),
padding: const EdgeInsets.only(left: 16.0), Expanded(
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.only(left: 16.0),
children: [ child: Column(
Text( crossAxisAlignment: CrossAxisAlignment.start,
title, children: [
maxLines: 1, Text(
overflow: TextOverflow.ellipsis, title,
style: TextStyle( maxLines: 1,
fontSize: 16, overflow: TextOverflow.ellipsis,
fontWeight: unreadMessages > 0 style: unreadMessages > 0
? FontWeight.w900 ? theme.textTheme.bodyLarge
: FontWeight.w500, : theme.textTheme.bodyMedium,
),
if (subTitle != null) ...[
Padding(
padding: const EdgeInsets.only(top: 3.0),
child: Text(
subTitle!,
style: unreadMessages > 0
? theme.textTheme.bodyLarge
: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
maxLines: 2,
), ),
), ),
if (subTitle != null)
Padding(
padding: const EdgeInsets.only(top: 3.0),
child: Text(
subTitle!,
style: TextStyle(
fontSize: 16,
fontWeight: unreadMessages > 0
? FontWeight.w500
: FontWeight.w300,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
], ],
), ],
), ),
), ),
Column( ),
mainAxisAlignment: MainAxisAlignment.start, Column(
crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.start,
children: [ crossAxisAlignment: CrossAxisAlignment.end,
if (lastUsed != null) // Check if lastUsed is not null children: [
Padding( if (lastUsed != null) ...[
padding: const EdgeInsets.only(bottom: 4.0), Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
lastUsed!,
style: const TextStyle(
color: Color(0xFFBBBBBB),
fontSize: 14,
),
),
),
],
if (unreadMessages > 0) ...[
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
),
child: Center(
child: Text( child: Text(
lastUsed!, unreadMessages.toString(),
style: const TextStyle( style: const TextStyle(
color: Color(0xFFBBBBBB),
fontSize: 14, fontSize: 14,
), ),
), ),
), ),
if (unreadMessages > 0) ...[ ),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
shape: BoxShape.circle,
),
child: Center(
child: Text(
unreadMessages.toString(),
style: const TextStyle(
fontSize: 14,
),
),
),
),
],
], ],
), ],
], ),
); ],
);
}
} }

View file

@ -59,7 +59,10 @@ Widget _createNewChatButton(
ChatTranslations translations, ChatTranslations translations,
) => ) =>
Padding( Padding(
padding: const EdgeInsets.fromLTRB(5, 24, 5, 24), padding: const EdgeInsets.symmetric(
vertical: 24,
horizontal: 5,
),
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
@ -74,7 +77,7 @@ Widget _createNewChatButton(
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
fontSize: 18, fontSize: 20,
), ),
), ),
), ),
@ -84,43 +87,43 @@ Widget _createMessageInput(
TextEditingController textEditingController, TextEditingController textEditingController,
Widget suffixIcon, Widget suffixIcon,
ChatTranslations translations, ChatTranslations translations,
) => BuildContext context,
TextField( ) {
textCapitalization: TextCapitalization.sentences, var theme = Theme.of(context);
controller: textEditingController, return TextField(
decoration: InputDecoration( textCapitalization: TextCapitalization.sentences,
enabledBorder: OutlineInputBorder( controller: textEditingController,
borderRadius: BorderRadius.circular(26.5), decoration: InputDecoration(
borderSide: const BorderSide( enabledBorder: OutlineInputBorder(
color: Colors.black, borderRadius: BorderRadius.circular(25),
), borderSide: const BorderSide(
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(26.5),
borderSide: const BorderSide(
color: Colors.black,
),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 30,
),
hintText: translations.messagePlaceholder,
hintStyle: const TextStyle(
fontWeight: FontWeight.normal,
color: Colors.black, color: Colors.black,
), ),
fillColor: Colors.white,
filled: true,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(26.5),
),
borderSide: BorderSide.none,
),
suffixIcon: suffixIcon,
), ),
); focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25),
borderSide: const BorderSide(
color: Colors.black,
),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 0,
horizontal: 30,
),
hintText: translations.messagePlaceholder,
hintStyle: theme.inputDecorationTheme.hintStyle,
fillColor: Colors.white,
filled: true,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(25),
),
borderSide: BorderSide.none,
),
suffixIcon: suffixIcon,
),
);
}
Widget _createChatRowContainer( Widget _createChatRowContainer(
Widget chatRow, Widget chatRow,
@ -130,7 +133,10 @@ Widget _createChatRowContainer(
vertical: 12.0, vertical: 12.0,
horizontal: 10.0, horizontal: 10.0,
), ),
child: chatRow, child: Container(
color: Colors.transparent,
child: chatRow,
),
); );
Widget _createImagePickerContainer( Widget _createImagePickerContainer(
@ -166,6 +172,7 @@ Widget _createImagePickerContainer(
Scaffold _createScaffold( Scaffold _createScaffold(
AppBar appbar, AppBar appbar,
Widget body, Widget body,
Color backgroundColor,
) => ) =>
Scaffold( Scaffold(
appBar: appbar, appBar: appbar,
@ -196,31 +203,32 @@ Widget _createGroupAvatar(
Widget _createNoChatsPlaceholder( Widget _createNoChatsPlaceholder(
ChatTranslations translations, ChatTranslations translations,
) => BuildContext context,
Center( ) {
child: Text( var theme = Theme.of(context);
translations.noChatsFound, return Center(
textAlign: TextAlign.center, child: Text(
style: const TextStyle( translations.noChatsFound,
color: Colors.white, textAlign: TextAlign.center,
fontSize: 18, style: theme.textTheme.bodySmall,
), ),
), );
); }
Widget _createNoUsersPlaceholder( Widget _createNoUsersPlaceholder(
ChatTranslations translations, ChatTranslations translations,
) => BuildContext context,
Center( ) {
child: Text( var theme = Theme.of(context);
translations.noUsersFound, return Padding(
textAlign: TextAlign.center, padding: const EdgeInsets.symmetric(vertical: 20),
style: const TextStyle( child: Text(
color: Colors.white, translations.noUsersFound,
fontSize: 18, textAlign: TextAlign.center,
), style: theme.textTheme.bodySmall,
), ),
); );
}
typedef ButtonBuilder = Widget Function( typedef ButtonBuilder = Widget Function(
BuildContext context, BuildContext context,
@ -232,6 +240,7 @@ typedef TextInputBuilder = Widget Function(
TextEditingController textEditingController, TextEditingController textEditingController,
Widget suffixIcon, Widget suffixIcon,
ChatTranslations translations, ChatTranslations translations,
BuildContext context,
); );
typedef ContainerBuilder = Widget Function( typedef ContainerBuilder = Widget Function(
@ -247,6 +256,7 @@ typedef ImagePickerContainerBuilder = Widget Function(
typedef ScaffoldBuilder = Scaffold Function( typedef ScaffoldBuilder = Scaffold Function(
AppBar appBar, AppBar appBar,
Widget body, Widget body,
Color backgroundColor,
); );
typedef UserAvatarBuilder = Widget Function( typedef UserAvatarBuilder = Widget Function(
@ -262,8 +272,10 @@ typedef GroupAvatarBuilder = Widget Function(
typedef NoChatsPlaceholderBuilder = Widget Function( typedef NoChatsPlaceholderBuilder = Widget Function(
ChatTranslations translations, ChatTranslations translations,
BuildContext context,
); );
typedef NoUsersPlaceholderBuilder = Widget Function( typedef NoUsersPlaceholderBuilder = Widget Function(
ChatTranslations translations, ChatTranslations translations,
BuildContext context,
); );

View file

@ -36,13 +36,17 @@ class ChatTranslations {
required this.uploadFile, required this.uploadFile,
required this.takePicture, required this.takePicture,
required this.anonymousUser, required this.anonymousUser,
required this.groupNameValidatorEmpty,
required this.groupNameValidatorTooLong,
required this.groupNameHintText,
required this.newGroupChatTitle,
}); });
/// Default translations for the chat component view /// Default translations for the chat component view
const ChatTranslations.empty({ const ChatTranslations.empty({
this.chatsTitle = 'Chats', this.chatsTitle = 'Chats',
this.chatsUnread = 'unread', this.chatsUnread = 'unread',
this.newChatButton = 'Start a chat', this.newChatButton = 'Start chat',
this.newGroupChatButton = 'Create a group chat', this.newGroupChatButton = 'Create a group chat',
this.newChatTitle = 'Start a chat', this.newChatTitle = 'Start a chat',
this.image = 'Image', this.image = 'Image',
@ -68,6 +72,11 @@ class ChatTranslations {
this.imagePickerTitle = 'Do you want to upload a file or take a picture?', this.imagePickerTitle = 'Do you want to upload a file or take a picture?',
this.uploadFile = 'UPLOAD FILE', this.uploadFile = 'UPLOAD FILE',
this.takePicture = 'TAKE PICTURE', this.takePicture = 'TAKE PICTURE',
this.groupNameHintText = 'Group chat name',
this.groupNameValidatorEmpty = 'Please enter a group chat name',
this.groupNameValidatorTooLong =
'Group name is too long, max 15 characters',
this.newGroupChatTitle = 'New Group Chat',
}); });
final String chatsTitle; final String chatsTitle;
@ -98,6 +107,10 @@ class ChatTranslations {
/// Shown when the user has no name /// Shown when the user has no name
final String anonymousUser; final String anonymousUser;
final String groupNameValidatorEmpty;
final String groupNameValidatorTooLong;
final String groupNameHintText;
final String newGroupChatTitle;
// copyWith method to override the default values // copyWith method to override the default values
ChatTranslations copyWith({ ChatTranslations copyWith({
@ -127,6 +140,10 @@ class ChatTranslations {
String? uploadFile, String? uploadFile,
String? takePicture, String? takePicture,
String? anonymousUser, String? anonymousUser,
String? groupNameValidatorEmpty,
String? groupNameValidatorTooLong,
String? groupNameHintText,
String? newGroupChatTitle,
}) => }) =>
ChatTranslations( ChatTranslations(
chatsTitle: chatsTitle ?? this.chatsTitle, chatsTitle: chatsTitle ?? this.chatsTitle,
@ -160,5 +177,9 @@ class ChatTranslations {
uploadFile: uploadFile ?? this.uploadFile, uploadFile: uploadFile ?? this.uploadFile,
takePicture: takePicture ?? this.takePicture, takePicture: takePicture ?? this.takePicture,
anonymousUser: anonymousUser ?? this.anonymousUser, anonymousUser: anonymousUser ?? this.anonymousUser,
groupNameValidatorEmpty: this.groupNameValidatorEmpty,
groupNameValidatorTooLong: this.groupNameValidatorTooLong,
groupNameHintText: this.groupNameHintText,
newGroupChatTitle: this.newGroupChatTitle,
); );
} }

View file

@ -168,8 +168,9 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
builder: (context, AsyncSnapshot<ChatModel> snapshot) { builder: (context, AsyncSnapshot<ChatModel> snapshot) {
var chatModel = snapshot.data; var chatModel = snapshot.data;
return Scaffold( return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar( appBar: AppBar(
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black, backgroundColor: theme.appBarTheme.backgroundColor,
iconTheme: theme.appBarTheme.iconTheme ?? iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white), const IconThemeData(color: Colors.white),
centerTitle: true, centerTitle: true,
@ -188,14 +189,6 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
children: chat == null children: chat == null
? [] ? []
: [ : [
if (chatModel is GroupChatModel) ...[
widget.options.groupAvatarBuilder(
chatModel.title,
chatModel.imageUrl,
36.0,
),
] else
...[],
Padding( Padding(
padding: (chatModel is GroupChatModel) padding: (chatModel is GroupChatModel)
? const EdgeInsets.only(left: 15.5) ? const EdgeInsets.only(left: 15.5)
@ -216,12 +209,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
? chatModel.user.firstName ?? ? chatModel.user.firstName ??
widget.translations.anonymousUser widget.translations.anonymousUser
: '', : '',
style: theme.appBarTheme.titleTextStyle ?? style: theme.appBarTheme.titleTextStyle,
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
), ),
), ),
], ],
@ -271,11 +259,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
.writeFirstMessageInGroupChat .writeFirstMessageInGroupChat
: widget : widget
.translations.writeMessageToStartChat, .translations.writeMessageToStartChat,
style: const TextStyle( style: theme.textTheme.bodySmall,
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Color.fromRGBO(33, 33, 33, 1),
),
), ),
), ),
...detailRows, ...detailRows,

View file

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_chat_view/flutter_chat_view.dart'; import 'package:flutter_chat_view/flutter_chat_view.dart';
import 'package:flutter_chat_view/src/services/profile_service.dart';
import 'package:flutter_profile/flutter_profile.dart'; import 'package:flutter_profile/flutter_profile.dart';
class ChatProfileScreen extends StatefulWidget { class ChatProfileScreen extends StatefulWidget {
@ -35,7 +34,6 @@ class ChatProfileScreen extends StatefulWidget {
class _ProfileScreenState extends State<ChatProfileScreen> { class _ProfileScreenState extends State<ChatProfileScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var hasUser = widget.userId == null; var hasUser = widget.userId == null;
var theme = Theme.of(context); var theme = Theme.of(context);
return FutureBuilder<dynamic>( return FutureBuilder<dynamic>(
@ -67,10 +65,10 @@ class _ProfileScreenState extends State<ChatProfileScreen> {
imageUrl: data.imageUrl, imageUrl: data.imageUrl,
); );
} }
return Scaffold( return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar( appBar: AppBar(
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black, backgroundColor: theme.appBarTheme.backgroundColor,
iconTheme: theme.appBarTheme.iconTheme ?? iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white), const IconThemeData(color: Colors.white),
title: Text( title: Text(
@ -81,32 +79,28 @@ class _ProfileScreenState extends State<ChatProfileScreen> {
: (data is GroupChatModel) : (data is GroupChatModel)
? data.title ? data.title
: '', : '',
style: theme.appBarTheme.titleTextStyle ?? style: theme.appBarTheme.titleTextStyle,
const TextStyle(
color: Colors.white,
),
), ),
), ),
body: snapshot.hasData body: snapshot.hasData
? ListView( ? ListView(
children: [ children: [
const SizedBox( Padding(
height: 10, padding: const EdgeInsets.symmetric(vertical: 20),
), child: Avatar(
SizedBox( user: user,
height: 200,
width: size.width,
child: ProfilePage(
user: user!,
itemBuilderOptions: ItemBuilderOptions(
readOnly: true,
),
service: ChatProfileService(),
), ),
), ),
const Divider(
color: Colors.white,
thickness: 10,
),
if (data is GroupChatModel) ...[ if (data is GroupChatModel) ...[
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 100), padding: const EdgeInsets.symmetric(
horizontal: 100,
vertical: 20,
),
child: Text( child: Text(
widget.translations.chatProfileUsers, widget.translations.chatProfileUsers,
style: const TextStyle( style: const TextStyle(

View file

@ -77,16 +77,10 @@ class _ChatScreenState extends State<ChatScreen> {
var theme = Theme.of(context); var theme = Theme.of(context);
return widget.options.scaffoldBuilder( return widget.options.scaffoldBuilder(
AppBar( AppBar(
backgroundColor: backgroundColor: theme.appBarTheme.backgroundColor,
theme.appBarTheme.backgroundColor ?? const Color(0xff212121),
title: Text( title: Text(
translations.chatsTitle, translations.chatsTitle,
style: theme.appBarTheme.titleTextStyle ?? style: theme.appBarTheme.titleTextStyle,
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
), ),
centerTitle: true, centerTitle: true,
actions: [ actions: [
@ -120,7 +114,7 @@ class _ChatScreenState extends State<ChatScreen> {
controller: controller, controller: controller,
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
padding: widget.options.paddingAroundChatList ?? padding: widget.options.paddingAroundChatList ??
const EdgeInsets.fromLTRB(28, 16, 28, 0), const EdgeInsets.symmetric(vertical: 16, horizontal: 28),
children: [ children: [
StreamBuilder<List<ChatModel>>( StreamBuilder<List<ChatModel>>(
stream: widget.service.chatOverviewService.getChatsStream(), stream: widget.service.chatOverviewService.getChatsStream(),
@ -138,157 +132,172 @@ class _ChatScreenState extends State<ChatScreen> {
return Center( return Center(
child: Text( child: Text(
translations.noChatsFound, translations.noChatsFound,
style: const TextStyle( style: theme.textTheme.bodySmall,
fontSize: 14,
fontWeight: FontWeight.w400,
),
), ),
); );
} else { } else {
_hasCalledOnNoChats = _hasCalledOnNoChats = false;
false; // Reset the flag if there are chats
} }
return Column( return Column(
children: [ children: [
for (ChatModel chat in (snapshot.data ?? []).where( for (ChatModel chat in (snapshot.data ?? []).where(
(chat) => !deletedChats.contains(chat.id), (chat) => !deletedChats.contains(chat.id),
)) ...[ )) ...[
Builder( Container(
builder: (context) => !(widget decoration: BoxDecoration(
.disableDismissForPermanentChats && border: Border(
!chat.canBeDeleted) bottom: BorderSide(
? Dismissible( color: Theme.of(context)
confirmDismiss: (_) async => .colorScheme
widget.deleteChatDialog .secondary
?.call(context, chat) ?? .withOpacity(0.3),
showModalBottomSheet( width: 0.5,
context: context, ),
builder: (BuildContext context) => ),
Container( ),
padding: const EdgeInsets.all(16.0), child: Builder(
child: Column( builder: (context) => !(widget
mainAxisSize: MainAxisSize.min, .disableDismissForPermanentChats &&
crossAxisAlignment: !chat.canBeDeleted)
CrossAxisAlignment.stretch, ? Dismissible(
children: [ confirmDismiss: (_) async =>
Text( widget.deleteChatDialog
chat.canBeDeleted ?.call(context, chat) ??
? translations showModalBottomSheet(
.deleteChatModalTitle context: context,
: translations builder: (BuildContext context) =>
.chatCantBeDeleted, Container(
textAlign: TextAlign.center, padding:
style: const TextStyle( const EdgeInsets.all(16.0),
fontSize: 24, child: Column(
fontWeight: FontWeight.bold, mainAxisSize: MainAxisSize.min,
), crossAxisAlignment:
), CrossAxisAlignment.stretch,
const SizedBox(height: 24), children: [
if (chat.canBeDeleted) Text(
Padding( chat.canBeDeleted
padding: const EdgeInsets ? translations
.symmetric( .deleteChatModalTitle
horizontal: 16, : translations
.chatCantBeDeleted,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight:
FontWeight.bold,
), ),
child: Text( ),
translations const SizedBox(height: 24),
.deleteChatModalDescription, if (chat.canBeDeleted)
textAlign: Padding(
TextAlign.center, padding: const EdgeInsets
style: const TextStyle( .symmetric(
fontSize: 18, horizontal: 16,
), ),
),
),
const SizedBox(
height: 24,
),
Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () =>
Navigator.of(
context,
).pop(false),
child: Text( child: Text(
translations translations
.deleteChatModalCancel, .deleteChatModalDescription,
textAlign:
TextAlign.center,
style: const TextStyle( style: const TextStyle(
color: Colors.black,
fontSize: 18, fontSize: 18,
), ),
), ),
), ),
if (chat.canBeDeleted) const SizedBox(
const SizedBox( height: 24,
width: 16, ),
), Row(
if (chat.canBeDeleted) mainAxisAlignment:
MainAxisAlignment
.center,
children: [
ElevatedButton( ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor:
Theme.of(context)
.primaryColor,
),
onPressed: () => onPressed: () =>
Navigator.of( Navigator.of(
context, context,
).pop( ).pop(false),
true,
),
child: Text( child: Text(
translations translations
.deleteChatModalConfirm, .deleteChatModalCancel,
style: style:
const TextStyle( const TextStyle(
color: Colors.white, color: Colors.black,
fontSize: 18, fontSize: 18,
), ),
), ),
), ),
], if (chat.canBeDeleted)
), const SizedBox(
], width: 16,
),
if (chat.canBeDeleted)
ElevatedButton(
style: ElevatedButton
.styleFrom(
backgroundColor:
Theme.of(
context,
).primaryColor,
),
onPressed: () =>
Navigator.of(
context,
).pop(
true,
),
child: Text(
translations
.deleteChatModalConfirm,
style:
const TextStyle(
color:
Colors.white,
fontSize: 18,
),
),
),
],
),
],
),
),
),
onDismissed: (_) {
setState(() {
deletedChats.add(chat.id!);
});
widget.onDeleteChat(chat);
},
background: Container(
color: Colors.red,
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
translations.deleteChatButton,
), ),
), ),
), ),
onDismissed: (_) {
setState(() {
deletedChats.add(chat.id!);
});
widget.onDeleteChat(chat);
},
background: Container(
color: Colors.red,
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
translations.deleteChatButton,
),
),
), ),
), key: ValueKey(
key: ValueKey( chat.id.toString(),
chat.id.toString(), ),
), child: ChatListItem(
child: ChatListItem( widget: widget,
chat: chat,
translations: translations,
dateFormatter: _dateFormatter,
),
)
: ChatListItem(
widget: widget, widget: widget,
chat: chat, chat: chat,
translations: translations, translations: translations,
dateFormatter: _dateFormatter, dateFormatter: _dateFormatter,
), ),
) ),
: ChatListItem(
widget: widget,
chat: chat,
translations: translations,
dateFormatter: _dateFormatter,
),
), ),
], ],
], ],
@ -308,6 +317,7 @@ class _ChatScreenState extends State<ChatScreen> {
), ),
], ],
), ),
theme.colorScheme.surface,
); );
} }
} }
@ -327,59 +337,52 @@ class ChatListItem extends StatelessWidget {
final DateFormatter _dateFormatter; final DateFormatter _dateFormatter;
@override @override
Widget build(BuildContext context) => Column( Widget build(BuildContext context) => GestureDetector(
children: [ onTap: () {
GestureDetector( widget.onPressChat(chat);
onTap: () => widget.onPressChat(chat), },
child: Container( child: widget.options.chatRowContainerBuilder(
color: Colors.transparent, (chat is PersonalChatModel)
child: widget.options.chatRowContainerBuilder( ? ChatRow(
(chat is PersonalChatModel) unreadMessages: chat.unreadMessages ?? 0,
? ChatRow( avatar: widget.options.userAvatarBuilder(
unreadMessages: chat.unreadMessages ?? 0, (chat as PersonalChatModel).user,
avatar: widget.options.userAvatarBuilder( 40.0,
(chat as PersonalChatModel).user, ),
40.0, title: (chat as PersonalChatModel).user.fullName ??
), translations.anonymousUser,
title: (chat as PersonalChatModel).user.fullName ?? subTitle: chat.lastMessage != null
translations.anonymousUser, ? chat.lastMessage is ChatTextMessageModel
subTitle: chat.lastMessage != null ? (chat.lastMessage! as ChatTextMessageModel).text
? chat.lastMessage is ChatTextMessageModel : '📷 '
? (chat.lastMessage! as ChatTextMessageModel) '${translations.image}'
.text : '',
: '📷 ' lastUsed: chat.lastUsed != null
'${translations.image}' ? _dateFormatter.format(
: '', date: chat.lastUsed!,
lastUsed: chat.lastUsed != null )
? _dateFormatter.format( : null,
date: chat.lastUsed!, )
) : ChatRow(
: null, title: (chat as GroupChatModel).title,
) unreadMessages: chat.unreadMessages ?? 0,
: ChatRow( subTitle: chat.lastMessage != null
title: (chat as GroupChatModel).title, ? chat.lastMessage is ChatTextMessageModel
unreadMessages: chat.unreadMessages ?? 0, ? (chat.lastMessage! as ChatTextMessageModel).text
subTitle: chat.lastMessage != null : '📷 '
? chat.lastMessage is ChatTextMessageModel '${translations.image}'
? (chat.lastMessage! as ChatTextMessageModel) : '',
.text avatar: widget.options.groupAvatarBuilder(
: '📷 ' (chat as GroupChatModel).title,
'${translations.image}' (chat as GroupChatModel).imageUrl,
: '', 40.0,
avatar: widget.options.groupAvatarBuilder( ),
(chat as GroupChatModel).title, lastUsed: chat.lastUsed != null
(chat as GroupChatModel).imageUrl, ? _dateFormatter.format(
40.0, date: chat.lastUsed!,
), )
lastUsed: chat.lastUsed != null : null,
? _dateFormatter.format( ),
date: chat.lastUsed!, ),
)
: null,
),
),
),
),
],
); );
} }

View file

@ -47,10 +47,11 @@ class _NewChatScreenState extends State<NewChatScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
return Scaffold( return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar( appBar: AppBar(
iconTheme: theme.appBarTheme.iconTheme ?? iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white), const IconThemeData(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black, backgroundColor: theme.appBarTheme.backgroundColor,
title: _buildSearchField(), title: _buildSearchField(),
actions: [ actions: [
_buildSearchIcon(), _buildSearchIcon(),
@ -114,7 +115,8 @@ class _NewChatScreenState extends State<NewChatScreen> {
} else if (snapshot.hasData) { } else if (snapshot.hasData) {
return _buildUserList(snapshot.data!); return _buildUserList(snapshot.data!);
} else { } else {
return Text(widget.translations.noUsersFound); return widget.options
.noUsersPlaceholderBuilder(widget.translations, context);
} }
}, },
), ),
@ -128,44 +130,28 @@ class _NewChatScreenState extends State<NewChatScreen> {
var theme = Theme.of(context); var theme = Theme.of(context);
return _isSearching return _isSearching
? Padding( ? TextField(
padding: const EdgeInsets.symmetric(vertical: 8.0), focusNode: _textFieldFocusNode,
child: TextField( onChanged: (value) {
focusNode: _textFieldFocusNode, setState(() {
onChanged: (value) { query = value;
setState(() { });
query = value; },
}); decoration: InputDecoration(
}, hintText: widget.translations.searchPlaceholder,
decoration: InputDecoration( hintStyle: theme.inputDecorationTheme.hintStyle,
hintText: widget.translations.searchPlaceholder, focusedBorder: UnderlineInputBorder(
hintStyle: theme.inputDecorationTheme.hintStyle ?? borderSide: BorderSide(
const TextStyle( color: theme.colorScheme.primary,
color: Colors.white,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: theme.inputDecorationTheme.focusedBorder?.borderSide
.color ??
Colors.white,
),
), ),
), ),
style: theme.inputDecorationTheme.hintStyle ??
const TextStyle(
color: Colors.white,
),
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
), ),
style: theme.inputDecorationTheme.hintStyle,
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
) )
: Text( : Text(
widget.translations.newChatTitle, widget.translations.newChatTitle,
style: theme.appBarTheme.titleTextStyle ?? style: theme.appBarTheme.titleTextStyle,
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
); );
} }
@ -191,6 +177,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
} }
Widget _buildUserList(List<ChatUserModel> users) { Widget _buildUserList(List<ChatUserModel> users) {
var theme = Theme.of(context);
var filteredUsers = users var filteredUsers = users
.where( .where(
(user) => (user) =>
@ -204,60 +191,71 @@ class _NewChatScreenState extends State<NewChatScreen> {
if (_textFieldFocusNode.hasFocus && query.isEmpty) { if (_textFieldFocusNode.hasFocus && query.isEmpty) {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 20.0), padding: const EdgeInsets.only(top: 20.0),
child: Center( child: Text(
child: Text( widget.translations.startTyping,
widget.translations.startTyping, style: theme.textTheme.bodySmall,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
), ),
); );
} }
if (filteredUsers.isEmpty) { if (filteredUsers.isEmpty) {
return widget.options.noChatsPlaceholderBuilder(widget.translations); return widget.options
.noUsersPlaceholderBuilder(widget.translations, context);
} }
var isPressed = false;
return ListView.builder( return ListView.builder(
itemCount: filteredUsers.length, itemCount: filteredUsers.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var user = filteredUsers[index]; var user = filteredUsers[index];
return GestureDetector( return Container(
child: widget.options.chatRowContainerBuilder( decoration: BoxDecoration(
Container( border: Border(
color: Colors.transparent, bottom: BorderSide(
child: Padding( color: theme.colorScheme.secondary.withOpacity(0.3),
padding: const EdgeInsets.symmetric(horizontal: 20.0), width: 0.5,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12),
child: widget.options.userAvatarBuilder(user, 40.0),
),
Expanded(
child: Container(
height: 40.0,
alignment: Alignment.centerLeft,
child: Text(
user.fullName ?? widget.translations.anonymousUser,
style: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w800,
),
),
),
),
],
),
), ),
), ),
), ),
onTap: () async { child: GestureDetector(
await widget.onPressCreateChat(user); child: widget.options.chatRowContainerBuilder(
}, Padding(
padding: widget.options.paddingAroundChatList ??
const EdgeInsets.symmetric(vertical: 8, horizontal: 28),
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: widget.options.userAvatarBuilder(user, 40.0),
),
Expanded(
child: Container(
height: 40.0,
alignment: Alignment.centerLeft,
child: Text(
user.fullName ??
widget.translations.anonymousUser,
style: theme.textTheme.bodyLarge,
),
),
),
],
),
),
),
),
),
onTap: () async {
if (!isPressed) {
isPressed = true;
await widget.onPressCreateChat(user);
isPressed = false;
}
},
),
); );
}, },
); );

View file

@ -33,34 +33,57 @@ class _NewGroupChatOverviewScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
var formKey = GlobalKey<FormState>();
var isPressed = false;
return Scaffold( return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar( appBar: AppBar(
iconTheme: theme.appBarTheme.iconTheme ?? iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white), const IconThemeData(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black, backgroundColor: theme.appBarTheme.backgroundColor,
title: const Text( title: Text(
'New Group Chat', widget.translations.newGroupChatTitle,
style: TextStyle(color: Colors.white), style: theme.appBarTheme.titleTextStyle,
), ),
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: TextField( child: Form(
controller: _textEditingController, key: formKey,
decoration: const InputDecoration( child: TextFormField(
hintText: 'Group chat name', controller: _textEditingController,
decoration: InputDecoration(
hintText: widget.translations.groupNameHintText,
hintStyle: theme.inputDecorationTheme.hintStyle,
),
validator: (value) {
if (value == null || value.isEmpty) {
return widget.translations.groupNameValidatorEmpty;
}
if (value.length > 15)
return widget.translations.groupNameValidatorTooLong;
return null;
},
), ),
), ),
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
onPressed: () async { onPressed: () async {
await widget.onPressCompleteGroupChatCreation( if (!isPressed) {
widget.users, isPressed = true;
_textEditingController.text, if (formKey.currentState!.validate()) {
); await widget.onPressCompleteGroupChatCreation(
widget.users,
_textEditingController.text,
);
}
isPressed = false;
}
}, },
child: const Icon(Icons.check_circle), child: const Icon(
Icons.check_circle,
),
), ),
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
); );

View file

@ -32,10 +32,11 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
return Scaffold( return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar( appBar: AppBar(
iconTheme: theme.appBarTheme.iconTheme ?? iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white), const IconThemeData(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black, backgroundColor: theme.appBarTheme.backgroundColor,
title: _buildSearchField(), title: _buildSearchField(),
actions: [ actions: [
_buildSearchIcon(), _buildSearchIcon(),
@ -51,10 +52,8 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
return Text('Error: ${snapshot.error}'); return Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) { } else if (snapshot.hasData) {
return _buildUserList(snapshot.data!); return _buildUserList(snapshot.data!);
} else {
return widget.options
.noChatsPlaceholderBuilder(widget.translations);
} }
return const SizedBox.shrink();
}, },
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
@ -72,44 +71,28 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
var theme = Theme.of(context); var theme = Theme.of(context);
return _isSearching return _isSearching
? Padding( ? TextField(
padding: const EdgeInsets.symmetric(vertical: 8.0), focusNode: _textFieldFocusNode,
child: TextField( onChanged: (value) {
focusNode: _textFieldFocusNode, setState(() {
onChanged: (value) { query = value;
setState(() { });
query = value; },
}); decoration: InputDecoration(
}, hintText: widget.translations.searchPlaceholder,
decoration: InputDecoration( hintStyle: theme.inputDecorationTheme.hintStyle,
hintText: widget.translations.searchPlaceholder, focusedBorder: UnderlineInputBorder(
hintStyle: theme.inputDecorationTheme.hintStyle ?? borderSide: BorderSide(
const TextStyle( color: theme.colorScheme.primary,
color: Colors.white,
),
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: theme.inputDecorationTheme.focusedBorder?.borderSide
.color ??
Colors.white,
),
), ),
), ),
style: theme.inputDecorationTheme.hintStyle ??
const TextStyle(
color: Colors.white,
),
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
), ),
style: theme.inputDecorationTheme.hintStyle,
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
) )
: Text( : Text(
widget.translations.newGroupChatButton, widget.translations.newGroupChatButton,
style: theme.appBarTheme.titleTextStyle ?? style: theme.appBarTheme.titleTextStyle,
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
); );
} }
@ -146,65 +129,113 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
.toList(); .toList();
if (filteredUsers.isEmpty) { if (filteredUsers.isEmpty) {
return widget.options.noChatsPlaceholderBuilder(widget.translations); return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
widget.options
.noUsersPlaceholderBuilder(widget.translations, context),
],
);
} }
return ListView.separated( return UserList(
itemCount: filteredUsers.length, filteredUsers: filteredUsers,
separatorBuilder: (context, index) => const Padding( selectedUserList: selectedUserList,
padding: EdgeInsets.symmetric(horizontal: 28.0), options: widget.options,
child: Divider(), translations: widget.translations,
), // Add Divider here
itemBuilder: (context, index) {
var user = filteredUsers[index];
var isSelected =
selectedUserList.any((selectedUser) => selectedUser == user);
return InkWell(
onTap: () {
setState(() {
if (selectedUserList.contains(user)) {
selectedUserList.remove(user);
} else {
selectedUserList.add(user);
}
debugPrint('The list of selected users is $selectedUserList');
});
},
child: Container(
color: isSelected ? Colors.amber.shade200 : Colors.white,
child: Padding(
padding:
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 30),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12),
child: widget.options.userAvatarBuilder(user, 40.0),
),
Expanded(
child: Container(
height: 40.0, // Adjust the height as needed
alignment: Alignment.centerLeft,
child: Text(
user.fullName ?? widget.translations.anonymousUser,
style: const TextStyle(
fontWeight: FontWeight.w800,
),
),
),
),
if (isSelected)
const Padding(
padding: EdgeInsets.only(right: 16.0),
child: Icon(Icons.check_circle, color: Colors.green),
),
],
),
),
),
);
},
); );
} }
} }
class UserList extends StatefulWidget {
const UserList({
required this.filteredUsers,
required this.selectedUserList,
required this.options,
required this.translations,
super.key,
});
final List<ChatUserModel> filteredUsers;
final List<ChatUserModel> selectedUserList;
final ChatOptions options;
final ChatTranslations translations;
@override
State<UserList> createState() => _UserListState();
}
class _UserListState extends State<UserList> {
@override
Widget build(BuildContext context) => ListView.builder(
itemCount: widget.filteredUsers.length,
itemBuilder: (context, index) {
var user = widget.filteredUsers[index];
var isSelected = widget.selectedUserList
.any((selectedUser) => selectedUser == user);
var theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: theme.colorScheme.secondary.withOpacity(0.3),
width: 0.5,
),
),
),
child: InkWell(
onTap: () {
setState(() {
if (widget.selectedUserList.contains(user)) {
widget.selectedUserList.remove(user);
} else {
widget.selectedUserList.add(user);
}
});
},
child: Padding(
padding: widget.options.paddingAroundChatList ??
const EdgeInsets.fromLTRB(28, 8, 28, 8),
child: Container(
color: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0,
horizontal: 30,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: widget.options.userAvatarBuilder(user, 40.0),
),
Expanded(
child: Container(
height: 40,
alignment: Alignment.centerLeft,
child: Text(
user.fullName ??
widget.translations.anonymousUser,
style: theme.textTheme.bodyLarge,
),
),
),
if (isSelected) ...[
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Icon(
Icons.check_circle,
color: theme.colorScheme.primary,
),
),
],
],
),
),
),
),
),
);
},
);
}

View file

@ -4,7 +4,7 @@
name: flutter_chat_view name: flutter_chat_view
description: A standard flutter package. description: A standard flutter package.
version: 2.0.0 version: 3.0.0
publish_to: none publish_to: none
@ -20,7 +20,7 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_chat url: https://github.com/Iconica-Development/flutter_chat
path: packages/flutter_chat_interface path: packages/flutter_chat_interface
ref: 2.0.0 ref: 3.0.0
cached_network_image: ^3.2.2 cached_network_image: ^3.2.2
flutter_image_picker: flutter_image_picker:
git: git:
@ -37,6 +37,6 @@ dev_dependencies:
flutter_iconica_analysis: flutter_iconica_analysis:
git: git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 6.0.0 ref: 7.0.0
flutter: flutter: