flyerhq / flutter_chat_ui

Actively maintained, community-driven chat UI implementation with an optional Firebase BaaS.
https://flyer.chat
Apache License 2.0
1.63k stars 702 forks source link

Received message flash gray. #559

Closed PawfectLLC closed 8 months ago

PawfectLLC commented 8 months ago

https://github.com/flyerhq/flutter_chat_ui/assets/137581900/a4eecad6-7a94-44b8-8e9d-67ffd621a4d2

the video shows the issue i am facing: sending message has no issue, but receiving message shows a flash screen. I used a bloc state to update the chat, and each time state is updated, chat is updated. I don't know why this happened, because both sending message and receiving message are from the same function, with the same state update mechanism. Can someone help me with this issue? Thanks a million.

below is my code:


class ChatView extends StatefulWidget {
  final String conversationId;
  final String currentUserId;
  final String otherUserId;
  final chat_bloc.ChatBloc chatBloc; // Add this line

  ChatView({required this.conversationId, required this.currentUserId, required this.otherUserId, required this.chatBloc}); // Modify this line

  @override
  _ChatViewState createState() => _ChatViewState();
}

class _ChatViewState extends State<ChatView>  with AutomaticKeepAliveClientMixin<ChatView>{
  late chat_bloc.ChatBloc _chatBloc;
  @override
  bool get wantKeepAlive => true; // Keep the state alive

  @override
  void initState() {
    super.initState();
    _chatBloc = widget.chatBloc; // Modify this line
  _chatBloc = context.read<chat_bloc.ChatBloc>();
}

  void _handleSendText(types.PartialText message) {
  // Access the current state to check if there's a message being replied to
  final currentState = _chatBloc.state;
  String repliedMessageId = '';
  String repliedTo = '';
  if (currentState.replyingToMessage != null) {
    // Extract necessary info from the replyingToMessage
    repliedMessageId = currentState.replyingToMessage!.id;
    repliedTo = currentState.replyingToMessage!.author.id; // or any other identifier
  }

  _chatBloc.add(chat_bloc.SendMessageEvent(
    conversationId: widget.conversationId,
    senderId: widget.currentUserId,
    receiverId: widget.otherUserId,
    message: types.TextMessage(
    author: types.User(id: widget.currentUserId),
    createdAt: DateTime.now().millisecondsSinceEpoch,
    id: const Uuid().v1(), // Use Uuid for unique message ID
    text: message.text,
    status: types.Status.sending,
    showStatus: true),
    text: message.text,
    messageType: MessageType.text,
    repliedMessage: repliedMessageId, // Now includes replied message info
    repliedTo: repliedTo,
    repliedMessageType: MessageType.text, 
    file: null
  ));
}

void _handleImageSelection() async {
  final ImageSource? imageSource = await showDialog<ImageSource>(
    context: context,
    builder: (context) => AlertDialog(
      title: Text("Select the image source"),
      actions: <Widget>[
        MaterialButton(
          child: Text("Camera"),
          onPressed: () => Navigator.pop(context, ImageSource.camera),
        ),
        MaterialButton(
          child: Text("Gallery"),
          onPressed: () => Navigator.pop(context, ImageSource.gallery),
        ),
      ],
    ),
  );

  if (imageSource == null) return; // User cancelled the dialog

  if (imageSource == ImageSource.camera) {
    final XFile? image = await ImagePicker().pickImage(source: imageSource);

    if (image != null) {
      _sendImageMessage(File(image.path));
    }
  } else {
    final List<XFile>? images = await ImagePicker().pickMultiImage();

    if (images != null) {
      for (XFile image in images) {
        _sendImageMessage(File(image.path));
      }
    }
  }
}

void _sendImageMessage(File file) async {
  MessageType messageType = MessageType.image;
  // Convert XFile to File
  final bytes = await file.readAsBytes();
  final image = await decodeImageFromList(bytes);

  final message = types.ImageMessage(
    author: types.User(id: widget.currentUserId),
    createdAt: DateTime.now().millisecondsSinceEpoch,
    height: image.height.toDouble(),
    id: const Uuid().v1(),
    name: file.path.split('/').last,
    size: bytes.length,
    uri: file.path,
    width: image.width.toDouble(),
    status: types.Status.sending,
    showStatus: true
  );

  _chatBloc.add(chat_bloc.SendMessageEvent(
    conversationId: widget.conversationId,
    senderId: widget.currentUserId,
    receiverId: widget.otherUserId,
    message: message, // For file messages, the actual message might be empty or a caption
    text: '',
    messageType: messageType,
    repliedMessage: '', // Include if replying to a message
    repliedTo: '',
    repliedMessageType: MessageType.text, 
    file: file
  ));
}

    void _handleMessageTap(BuildContext context, types.Message message) async {
  if (message is types.FileMessage) {
    var localPath = message.uri;

    if (message.uri.startsWith('http')) {
      try {
        final client = http.Client();
        final request = await client.get(Uri.parse(message.uri));
        final bytes = request.bodyBytes;
        final documentsDir = (await getApplicationDocumentsDirectory()).path;
        localPath = '$documentsDir/${message.name}';

        if (!File(localPath).existsSync()) {
          final file = File(localPath);
          await file.writeAsBytes(bytes);
        }
      } catch (e) {
        print('Error downloading file: $e');
        return;
      }
    }

    await OpenFilex.open(localPath);
  } else if (message is types.TextMessage) {
    final url = _extractUrl(message.text);
    final link = url != null ? Uri.parse(url) : null;
    if (url != null) {
      if (await canLaunchUrl(link!)) {
        await launchUrl(link);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Could not launch $url')),
        );
      }
    }
  }
}

String? _extractUrl(String text) {
  final urlPattern = RegExp(r'\bhttps?:\/\/\S+'); // Simple URL pattern
  final match = urlPattern.firstMatch(text);
  return match?.group(0); // Returns the first URL found, if any
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
      title: BlocBuilder<chat_bloc.ChatBloc, chat_bloc.ChatState>(
        builder: (context, state) {
          // Find the conversation by ID
          var conversation = state.conversations.firstWhere((element) => element.id == widget.conversationId);
          // Use the other user's username from the conversation for the title
          return Text(  conversation?.otherUsername ?? 'Chat');
        },
      ),
      leading: IconButton(
        icon: Icon(Icons.arrow_back),
        onPressed: () => Navigator.of(context).pop(),
      ),
    ),

       body: BlocBuilder<chat_bloc.ChatBloc, chat_bloc.ChatState>(
        builder: (context, state) {
          if (state.isLoading) {
            //return Center(child: CircularProgressIndicator());
            return Center(child: Text("No conversations found"));
          } else if (state.errorMessage != null) {
            //return Center(child: Text(state.errorMessage!));
            return Center(child: Text("No conversations found"));
          } else 
          if (state.conversations.isNotEmpty) {
            return chat.Chat(
              messages: state.conversations.where((element) => element.id == widget.conversationId).first.messages,
              onSendPressed:  (partialText) => _handleSendText(partialText),
              //user: types.User(id: state.conversations.where((element) => element.id == widget.conversationId).first.otherUsername),
              user: types.User(id: widget.currentUserId),
              showUserAvatars: true,
              //onEndReached: _loadMoreMessages,
              onAttachmentPressed: _handleImageSelection,
              onMessageTap: _handleMessageTap,

            );
          } 
          else {
            return Center(child: Text("No conversations found"));
          }
        },
      ),
    );
  }

  onTapBack(BuildContext context) {
    Navigator.pop(context);
  }
  onTapError(BuildContext context) {
  showModalBottomSheet(
      context: context,
      builder: (_) => DetailReportBottomsheet.builder(context),
      isScrollControlled: true);
  }

}
PawfectLLC commented 8 months ago

And I have tried to change state.loading part to a chat as well. didn't work either.

builder: (context, state) {
          if (state.isLoading) {
            //return Center(child: CircularProgressIndicator());
            return chat.Chat(
              messages: state.conversations != null? state.conversations.where((element) => element.id == widget.conversationId).first.messages: [],
              onSendPressed:  (partialText) => _handleSendText(partialText),
              //user: types.User(id: state.conversations.where((element) => element.id == widget.conversationId).first.otherUsername),
              user: types.User(id: widget.currentUserId),
              showUserAvatars: true,
              //onEndReached: _loadMoreMessages,
              onAttachmentPressed: _handleImageSelection,
              onMessageTap: _handleMessageTap,

            );
          } else if (state.errorMessage != null) {
            //return Center(child: Text(state.errorMessage!));
            return Center(child: Text("No conversations found"));
          } else 
          if (state.conversations.isNotEmpty) {
            return chat.Chat(
              messages: state.conversations.where((element) => element.id == widget.conversationId).first.messages,
              onSendPressed:  (partialText) => _handleSendText(partialText),
              //user: types.User(id: state.conversations.where((element) => element.id == widget.conversationId).first.otherUsername),
              user: types.User(id: widget.currentUserId),
              showUserAvatars: true,
              //onEndReached: _loadMoreMessages,
              onAttachmentPressed: _handleImageSelection,
              onMessageTap: _handleMessageTap,

            );
          } 
          else {
            return Center(child: Text("No conversations found"));
          }
        }
demchenkoalex commented 8 months ago

where does this grey loading screen comes from? it is not a part of the library for sure

PawfectLLC commented 8 months ago

where does this grey loading screen comes from? it is not a part of the library for sure

Hi Alex, thanks for getting back to me. I have no idea where this screen is coming from, as you can see in my code. There’s nowhere that I have called a loading screen. The gray loading screen happened after I called Chat. Could it be somewhere inside the library where it could call the system’s loading screen? Thanks!

demchenkoalex commented 8 months ago

No, never coded any loading screens 😕 I also have example with firebase where messages come and I never saw any loading screens either

PawfectLLC commented 8 months ago

AH! Thank you so much for the clarification! It is coming from my end of getting user's avatar. Finally figured it out! Thank you so much!