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
- Add a serviceBuilder to the userstory configuration

View file

@ -1,28 +1,9 @@
# This file configures the analyzer, which statically analyzes Dart code to
# 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`.
include: package:flutter_iconica_analysis/analysis_options.yaml
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
# Possible to overwrite the rules from the package
analyzer:
exclude:
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:
# 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_chat/flutter_chat.dart';
import "package:flutter/material.dart";
import "package:flutter_chat/flutter_chat.dart";
void main(List<String> args) async {
WidgetsFlutterBinding.ensureInitialized();
@ -11,25 +11,22 @@ class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
Widget build(BuildContext context) => const MaterialApp(
home: Home(),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: chatNavigatorUserStory(context,
Widget build(BuildContext context) => Center(
child: chatNavigatorUserStory(
context,
configuration: ChatUserStoryConfiguration(
chatService: LocalChatService(),
chatOptionsBuilder: (ctx) => ChatOptions(
noChatsPlaceholderBuilder: (translations) =>
Text(translations.noUsersFound),
))));
}
chatOptionsBuilder: (ctx) => const ChatOptions(),
),
),
);
}

View file

@ -16,11 +16,23 @@ dependencies:
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:
flutter_test:
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:
uses-material-design: true

View file

@ -5,10 +5,10 @@
// 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.
import 'package:flutter_test/flutter_test.dart';
import "package:flutter_test/flutter_test.dart";
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
testWidgets("Counter increments smoke test", (WidgetTester tester) async {
expect(true, true);
});
}

View file

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

View file

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

View file

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

View file

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

View file

@ -4,6 +4,6 @@
///
library flutter_chat_interface;
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/service/service.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/service/service.dart";

View file

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

View file

@ -3,7 +3,7 @@
//
// 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 {
ChatModelInterface copyWith();

View file

@ -3,7 +3,7 @@
//
// 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.
abstract class ChatImageMessageModelInterface extends ChatMessageModel {

View file

@ -3,7 +3,7 @@
//
// 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 {
ChatUserModel get sender;

View file

@ -3,7 +3,7 @@
//
// 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 {
ChatTextMessageModelInterface({

View file

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

View file

@ -3,7 +3,7 @@
//
// 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 {
GroupChatModelInterface({

View file

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

View file

@ -3,7 +3,7 @@
//
// 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 {
PersonalChatModelInterface({

View file

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_chat_interface/flutter_chat_interface.dart';
import "dart:typed_data";
import "package:flutter/material.dart";
import "package:flutter_chat_interface/flutter_chat_interface.dart";
/// An abstract class defining the interface for a chat detail service.
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 {
/// Retrieves a stream of chats.

View file

@ -1,5 +1,5 @@
// 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 {
final ChatUserService chatUserService;

View file

@ -1,4 +1,4 @@
export 'chat_detail_service.dart';
export 'chat_overview_service.dart';
export 'chat_service.dart';
export 'user_service.dart';
export "chat_detail_service.dart";
export "chat_overview_service.dart";
export "chat_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 {
/// Retrieves a user based on the ID.

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,9 @@ class ChatRow extends StatelessWidget {
final Widget? avatar;
@override
Widget build(BuildContext context) => Row(
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
@ -47,29 +49,24 @@ class ChatRow extends StatelessWidget {
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: unreadMessages > 0
? FontWeight.w900
: FontWeight.w500,
style: unreadMessages > 0
? theme.textTheme.bodyLarge
: theme.textTheme.bodyMedium,
),
),
if (subTitle != null)
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,
),
style: unreadMessages > 0
? theme.textTheme.bodyLarge
: theme.textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
],
),
),
),
@ -77,7 +74,7 @@ class ChatRow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (lastUsed != null) // Check if lastUsed is not null
if (lastUsed != null) ...[
Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Text(
@ -88,6 +85,7 @@ class ChatRow extends StatelessWidget {
),
),
),
],
if (unreadMessages > 0) ...[
Container(
width: 20,
@ -110,4 +108,5 @@ class ChatRow extends StatelessWidget {
),
],
);
}
}

View file

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

View file

@ -36,13 +36,17 @@ class ChatTranslations {
required this.uploadFile,
required this.takePicture,
required this.anonymousUser,
required this.groupNameValidatorEmpty,
required this.groupNameValidatorTooLong,
required this.groupNameHintText,
required this.newGroupChatTitle,
});
/// Default translations for the chat component view
const ChatTranslations.empty({
this.chatsTitle = 'Chats',
this.chatsUnread = 'unread',
this.newChatButton = 'Start a chat',
this.newChatButton = 'Start chat',
this.newGroupChatButton = 'Create a group chat',
this.newChatTitle = 'Start a chat',
this.image = 'Image',
@ -68,6 +72,11 @@ class ChatTranslations {
this.imagePickerTitle = 'Do you want to upload a file or take a picture?',
this.uploadFile = 'UPLOAD FILE',
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;
@ -98,6 +107,10 @@ class ChatTranslations {
/// Shown when the user has no name
final String anonymousUser;
final String groupNameValidatorEmpty;
final String groupNameValidatorTooLong;
final String groupNameHintText;
final String newGroupChatTitle;
// copyWith method to override the default values
ChatTranslations copyWith({
@ -127,6 +140,10 @@ class ChatTranslations {
String? uploadFile,
String? takePicture,
String? anonymousUser,
String? groupNameValidatorEmpty,
String? groupNameValidatorTooLong,
String? groupNameHintText,
String? newGroupChatTitle,
}) =>
ChatTranslations(
chatsTitle: chatsTitle ?? this.chatsTitle,
@ -160,5 +177,9 @@ class ChatTranslations {
uploadFile: uploadFile ?? this.uploadFile,
takePicture: takePicture ?? this.takePicture,
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) {
var chatModel = snapshot.data;
return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar(
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black,
backgroundColor: theme.appBarTheme.backgroundColor,
iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white),
centerTitle: true,
@ -188,14 +189,6 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
children: chat == null
? []
: [
if (chatModel is GroupChatModel) ...[
widget.options.groupAvatarBuilder(
chatModel.title,
chatModel.imageUrl,
36.0,
),
] else
...[],
Padding(
padding: (chatModel is GroupChatModel)
? const EdgeInsets.only(left: 15.5)
@ -216,12 +209,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
? chatModel.user.firstName ??
widget.translations.anonymousUser
: '',
style: theme.appBarTheme.titleTextStyle ??
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
style: theme.appBarTheme.titleTextStyle,
),
),
],
@ -271,11 +259,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
.writeFirstMessageInGroupChat
: widget
.translations.writeMessageToStartChat,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w400,
color: Color.fromRGBO(33, 33, 33, 1),
),
style: theme.textTheme.bodySmall,
),
),
...detailRows,

View file

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

View file

@ -77,16 +77,10 @@ class _ChatScreenState extends State<ChatScreen> {
var theme = Theme.of(context);
return widget.options.scaffoldBuilder(
AppBar(
backgroundColor:
theme.appBarTheme.backgroundColor ?? const Color(0xff212121),
backgroundColor: theme.appBarTheme.backgroundColor,
title: Text(
translations.chatsTitle,
style: theme.appBarTheme.titleTextStyle ??
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
style: theme.appBarTheme.titleTextStyle,
),
centerTitle: true,
actions: [
@ -120,7 +114,7 @@ class _ChatScreenState extends State<ChatScreen> {
controller: controller,
physics: const AlwaysScrollableScrollPhysics(),
padding: widget.options.paddingAroundChatList ??
const EdgeInsets.fromLTRB(28, 16, 28, 0),
const EdgeInsets.symmetric(vertical: 16, horizontal: 28),
children: [
StreamBuilder<List<ChatModel>>(
stream: widget.service.chatOverviewService.getChatsStream(),
@ -138,22 +132,30 @@ class _ChatScreenState extends State<ChatScreen> {
return Center(
child: Text(
translations.noChatsFound,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
),
style: theme.textTheme.bodySmall,
),
);
} else {
_hasCalledOnNoChats =
false; // Reset the flag if there are chats
_hasCalledOnNoChats = false;
}
return Column(
children: [
for (ChatModel chat in (snapshot.data ?? []).where(
(chat) => !deletedChats.contains(chat.id),
)) ...[
Builder(
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.3),
width: 0.5,
),
),
),
child: Builder(
builder: (context) => !(widget
.disableDismissForPermanentChats &&
!chat.canBeDeleted)
@ -165,7 +167,8 @@ class _ChatScreenState extends State<ChatScreen> {
context: context,
builder: (BuildContext context) =>
Container(
padding: const EdgeInsets.all(16.0),
padding:
const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
@ -180,7 +183,8 @@ class _ChatScreenState extends State<ChatScreen> {
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
fontWeight:
FontWeight.bold,
),
),
const SizedBox(height: 24),
@ -205,7 +209,8 @@ class _ChatScreenState extends State<ChatScreen> {
),
Row(
mainAxisAlignment:
MainAxisAlignment.center,
MainAxisAlignment
.center,
children: [
ElevatedButton(
onPressed: () =>
@ -215,7 +220,8 @@ class _ChatScreenState extends State<ChatScreen> {
child: Text(
translations
.deleteChatModalCancel,
style: const TextStyle(
style:
const TextStyle(
color: Colors.black,
fontSize: 18,
),
@ -230,8 +236,9 @@ class _ChatScreenState extends State<ChatScreen> {
style: ElevatedButton
.styleFrom(
backgroundColor:
Theme.of(context)
.primaryColor,
Theme.of(
context,
).primaryColor,
),
onPressed: () =>
Navigator.of(
@ -244,7 +251,8 @@ class _ChatScreenState extends State<ChatScreen> {
.deleteChatModalConfirm,
style:
const TextStyle(
color: Colors.white,
color:
Colors.white,
fontSize: 18,
),
),
@ -290,6 +298,7 @@ class _ChatScreenState extends State<ChatScreen> {
dateFormatter: _dateFormatter,
),
),
),
],
],
);
@ -308,6 +317,7 @@ class _ChatScreenState extends State<ChatScreen> {
),
],
),
theme.colorScheme.surface,
);
}
}
@ -327,12 +337,10 @@ class ChatListItem extends StatelessWidget {
final DateFormatter _dateFormatter;
@override
Widget build(BuildContext context) => Column(
children: [
GestureDetector(
onTap: () => widget.onPressChat(chat),
child: Container(
color: Colors.transparent,
Widget build(BuildContext context) => GestureDetector(
onTap: () {
widget.onPressChat(chat);
},
child: widget.options.chatRowContainerBuilder(
(chat is PersonalChatModel)
? ChatRow(
@ -345,8 +353,7 @@ class ChatListItem extends StatelessWidget {
translations.anonymousUser,
subTitle: chat.lastMessage != null
? chat.lastMessage is ChatTextMessageModel
? (chat.lastMessage! as ChatTextMessageModel)
.text
? (chat.lastMessage! as ChatTextMessageModel).text
: '📷 '
'${translations.image}'
: '',
@ -361,8 +368,7 @@ class ChatListItem extends StatelessWidget {
unreadMessages: chat.unreadMessages ?? 0,
subTitle: chat.lastMessage != null
? chat.lastMessage is ChatTextMessageModel
? (chat.lastMessage! as ChatTextMessageModel)
.text
? (chat.lastMessage! as ChatTextMessageModel).text
: '📷 '
'${translations.image}'
: '',
@ -378,8 +384,5 @@ class ChatListItem extends StatelessWidget {
: null,
),
),
),
),
],
);
}

View file

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

View file

@ -33,34 +33,57 @@ class _NewGroupChatOverviewScreenState
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var formKey = GlobalKey<FormState>();
var isPressed = false;
return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar(
iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black,
title: const Text(
'New Group Chat',
style: TextStyle(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor,
title: Text(
widget.translations.newGroupChatTitle,
style: theme.appBarTheme.titleTextStyle,
),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
child: Form(
key: formKey,
child: TextFormField(
controller: _textEditingController,
decoration: const InputDecoration(
hintText: 'Group chat name',
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(
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async {
if (!isPressed) {
isPressed = true;
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,
);

View file

@ -32,10 +32,11 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
Widget build(BuildContext context) {
var theme = Theme.of(context);
return Scaffold(
backgroundColor: theme.colorScheme.surface,
appBar: AppBar(
iconTheme: theme.appBarTheme.iconTheme ??
const IconThemeData(color: Colors.white),
backgroundColor: theme.appBarTheme.backgroundColor ?? Colors.black,
backgroundColor: theme.appBarTheme.backgroundColor,
title: _buildSearchField(),
actions: [
_buildSearchIcon(),
@ -51,10 +52,8 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
return Text('Error: ${snapshot.error}');
} else if (snapshot.hasData) {
return _buildUserList(snapshot.data!);
} else {
return widget.options
.noChatsPlaceholderBuilder(widget.translations);
}
return const SizedBox.shrink();
},
),
floatingActionButton: FloatingActionButton(
@ -72,9 +71,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
var theme = Theme.of(context);
return _isSearching
? Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TextField(
? TextField(
focusNode: _textFieldFocusNode,
onChanged: (value) {
setState(() {
@ -83,33 +80,19 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
},
decoration: InputDecoration(
hintText: widget.translations.searchPlaceholder,
hintStyle: theme.inputDecorationTheme.hintStyle ??
const TextStyle(
color: Colors.white,
),
hintStyle: theme.inputDecorationTheme.hintStyle,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: theme.inputDecorationTheme.focusedBorder?.borderSide
.color ??
Colors.white,
color: theme.colorScheme.primary,
),
),
),
style: theme.inputDecorationTheme.hintStyle ??
const TextStyle(
color: Colors.white,
),
style: theme.inputDecorationTheme.hintStyle,
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
),
)
: Text(
widget.translations.newGroupChatButton,
style: theme.appBarTheme.titleTextStyle ??
TextStyle(
fontWeight: FontWeight.w800,
fontSize: 24,
color: Theme.of(context).primaryColor,
),
style: theme.appBarTheme.titleTextStyle,
);
}
@ -146,65 +129,113 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
.toList();
if (filteredUsers.isEmpty) {
return widget.options.noChatsPlaceholderBuilder(widget.translations);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
widget.options
.noUsersPlaceholderBuilder(widget.translations, context),
],
);
}
return ListView.separated(
itemCount: filteredUsers.length,
separatorBuilder: (context, index) => const Padding(
padding: EdgeInsets.symmetric(horizontal: 28.0),
child: Divider(),
), // Add Divider here
itemBuilder: (context, index) {
var user = filteredUsers[index];
var isSelected =
selectedUserList.any((selectedUser) => selectedUser == user);
return UserList(
filteredUsers: filteredUsers,
selectedUserList: selectedUserList,
options: widget.options,
translations: widget.translations,
);
}
}
return InkWell(
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 (selectedUserList.contains(user)) {
selectedUserList.remove(user);
if (widget.selectedUserList.contains(user)) {
widget.selectedUserList.remove(user);
} else {
selectedUserList.add(user);
widget.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),
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.only(left: 12.0, right: 12),
padding: const EdgeInsets.symmetric(horizontal: 12),
child: widget.options.userAvatarBuilder(user, 40.0),
),
Expanded(
child: Container(
height: 40.0, // Adjust the height as needed
height: 40,
alignment: Alignment.centerLeft,
child: Text(
user.fullName ?? widget.translations.anonymousUser,
style: const TextStyle(
fontWeight: FontWeight.w800,
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,
),
if (isSelected)
const Padding(
padding: EdgeInsets.only(right: 16.0),
child: Icon(Icons.check_circle, color: Colors.green),
),
],
],
),
),
),
),
),
);
},
);
}
}

View file

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