Fliin
On this page

Screens / messaging

Chat thread

Shows a conversation anchored at the bottom, grouped by author and day, with older messages loaded while scrolling up, optimistic sending with retry, typing, and copy, reply or delete.

16 of 16 variants pass
Chat thread, thread state
Platform
Brightness
Text scale
Direction

Import

Chat thread is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add chat

Also installs: alert, app-bar, composer, confirm-delete, core, empty-state, link-button, message-bubble, separator, skeleton, spinner, avatar, button, icons

Examples

Thread

A conversation and its messages, grouped by day and by author. standalone holds its own controller over the service you pass; give it yours to reach your backend.

Chat thread, thread state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatThreadExample extends StatelessWidget {
  const ChatThreadExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: ChatThread(messages: sampleChatMessages),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

Sending

Messages written offline wait to be sent, a notice says so, and the other person is typing.

Chat thread, sending state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatSendingExample extends StatelessWidget {
  const ChatSendingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: ChatThread(
        messages: sampleChatSending,
        typing: const [sampleChatPerson],
        notice: ChatNotice.sendOffline,
      ),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

Loading

While the service answers, placeholders keep the shape of the messages.

Chat thread, loading state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatLoadingExample extends StatelessWidget {
  const ChatLoadingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: const ChatLoading(),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

Empty

A conversation with no message yet: the composer waits for the first one.

Chat thread, empty state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatEmptyExample extends StatelessWidget {
  const ChatEmptyExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: const ChatThread(messages: []),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

Error

When loading fails, the screen says so and offers to try again.

Chat thread, error state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatErrorExample extends StatelessWidget {
  const ChatErrorExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: const ChatFailed(),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

Offline

Without a connection, the screen asks to reconnect rather than showing an error.

Chat thread, offline state
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatOfflineExample extends StatelessWidget {
  const ChatOfflineExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: const ChatOffline(),
      onBack: () {},
      clock: () => sampleChatNow,
    );
  }
}

About

A conversation about an order: header pins the order under the bar, actions adds a search to the bar, beside the avatar.

Chat thread, about state
import 'package:your_app/ui/fliin/components/app_bar/fl_app_bar.dart';
import 'package:your_app/ui/fliin/components/list_row/fl_list_row.dart';
import 'package:your_app/ui/fliin/core/fliin.dart';
import 'package:your_app/ui/fliin/icons/fl_icons.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatAboutExample extends StatelessWidget {
  const ChatAboutExample({super.key});

  @override
  Widget build(BuildContext context) {
    return ChatScreen.standalone(
      title: sampleChatPerson.name,
      initials: sampleChatPerson.initials,
      service: SampleChatService(latency: const Duration(milliseconds: 600), clock: () => sampleChatNow),
      initial: ChatThread(messages: sampleChatMessages),
      actions: [FlAppBarAction(icon: FlIcon(FlIcons.magnifier), label: 'Search the conversation', onPressed: () {})],
      header: FlListRow(
        leading: FlIcon(FlIcons.shoppingCart),
        title: 'Order FL-2026-10482',
        subtitle: 'Delivered today · 137,90 €',
        trailing: const FlListRowChevron(),
        onPressed: () {},
      ),
      onBack: () {},
      onProfile: () {},
      clock: () => sampleChatNow,
    );
  }
}

Routing

fliin add chat prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/chat_route.dart, next to chat_page.dart.
import 'package:go_router/go_router.dart';

import 'chat_page.dart';

/// Add chatRoute to the routes of your GoRouter.
final chatRoute = GoRoute(
  path: '/chat',
  builder: (context, state) => ChatScreenPage(
    // Alone in the stack (deep link, go): your app's home.
    onBack: () => context.canPop() ? context.pop() : context.go('/'),
  ),
);

navigator

// lib/chat_route.dart, next to chat_page.dart.
import 'package:flutter/material.dart';

import 'chat_page.dart';

/// Opens “Chat thread”: `Navigator.of(context).push(chatRoute())`.
Route<void> chatRoute() => MaterialPageRoute<void>(
  builder: (context) => ChatScreenPage(
    onBack: () => Navigator.of(context).maybePop(),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add chat prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/chat_page.dart
// Your backend: implement ChatService (chat_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';

class ChatScreenPage extends StatelessWidget {
  const ChatScreenPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => ChatScreen.standalone(
    title: sampleChatPerson.name,
    service: SampleChatService(),
    onBack: onBack,
  );
}

provider

// lib/chat_page.dart
// Your backend: implement ChatService (chat_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

class ChatScreenPage extends StatelessWidget {
  const ChatScreenPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => ChatController(service: SampleChatService())..load(),
    child: Builder(
      builder: (context) => ChatScreen(
        title: sampleChatPerson.name,
        controller: context.read<ChatController>(),
        onBack: onBack,
      ),
    ),
  );
}

riverpod

// lib/chat_page.dart
// Your backend: implement ChatService (chat_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final chatProvider = Provider.autoDispose<ChatController>((ref) {
  final controller = ChatController(service: SampleChatService())..load();
  ref.onDispose(controller.dispose);
  return controller;
});

class ChatScreenPage extends ConsumerWidget {
  const ChatScreenPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context, WidgetRef ref) => ChatScreen(
    title: sampleChatPerson.name,
    controller: ref.watch(chatProvider),
    onBack: onBack,
  );
}

bloc

// lib/chat_page.dart
// Your backend: implement ChatService (chat_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/chat/chat_controller.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_data.dart';
import 'package:your_app/ui/fliin/screens/chat/chat_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class ChatCubit extends Cubit<({ChatState state, int version})> {
  ChatCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final ChatController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class ChatScreenPage extends StatelessWidget {
  const ChatScreenPage({
    super.key,
    required this.onBack,
  });

  final VoidCallback onBack;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => ChatCubit(ChatController(service: SampleChatService())..load()),
    child: Builder(
      builder: (context) => ChatScreen(
        title: sampleChatPerson.name,
        controller: context.read<ChatCubit>().controller,
        onBack: onBack,
      ),
    ),
  );
}

API

ChatScreen: class extends StatefulWidget
ChatScreen.new: const new({super.key, required String title, required ChatController controller, bool group, String? subtitle, String? initials, ImageProvider? avatar, VoidCallback? onBack, VoidCallback? onProfile, VoidCallback? onAttach, DateTime Function() clock, List<FlAppBarAction> actions, Widget? header})
ChatScreen.standalone: const new({super.key, required String title, required ChatService service, ChatState? initial, bool group, String? subtitle, String? initials, ImageProvider? avatar, VoidCallback? onBack, VoidCallback? onProfile, VoidCallback? onAttach, DateTime Function() clock, List<FlAppBarAction> actions, Widget? header})
ChatScreen.title: get String
ChatScreen.controller: get ChatController?
ChatScreen.service: get ChatService?
ChatScreen.initial: get ChatState?
ChatScreen.group: get bool
ChatScreen.subtitle: get String?
ChatScreen.initials: get String?
ChatScreen.avatar: get ImageProvider?
ChatScreen.onBack: get VoidCallback?
ChatScreen.onProfile: get VoidCallback?
ChatScreen.onAttach: get VoidCallback?
ChatScreen.clock: get DateTime Function()
ChatScreen.actions: get List<FlAppBarAction>
ChatScreen.header: get Widget?
ChatScreen.createState: State<ChatScreen>()
ChatState: sealed class permits {ChatFailed, ChatLoading, ChatOffline, ChatThread}
ChatState.new: const new()
ChatLoading: final class extends ChatState
ChatLoading.new: const new()
ChatNotice: enum {sendOffline, sendFailed, deleteFailed}
ChatNotice.sendOffline: static final ChatNotice
ChatNotice.sendFailed: static final ChatNotice
ChatNotice.deleteFailed: static final ChatNotice
ChatThread: final class extends ChatState
ChatThread.new: const new({required List<ChatMessage> messages, bool hasOlder, bool loadingOlder, bool olderFailed, List<ChatPerson> typing, ChatNotice? notice, ReceivedMessage? arrival})
ChatThread.messages: get List<ChatMessage>
ChatThread.hasOlder: get bool
ChatThread.loadingOlder: get bool
ChatThread.olderFailed: get bool
ChatThread.typing: get List<ChatPerson>
ChatThread.notice: get ChatNotice?
ChatThread.arrival: get ReceivedMessage?
ChatFailed: final class extends ChatState
ChatFailed.new: const new()
ChatOffline: final class extends ChatState
ChatOffline.new: const new()
ChatController: class extends ChangeNotifier
ChatController.new: new({required ChatService service, ChatState initial, DateTime Function() clock, String Function()? newLocalId})
ChatController.service: get ChatService
ChatController.clock: get DateTime Function()
ChatController.newLocalId: get String Function()
ChatController.state: get ChatState
ChatController.load: Future<void>()
ChatController.loadOlder: Future<bool>()
ChatController.send: Future<bool>(String text, {ChatQuote? replyTo})
ChatController.retry: Future<bool>(String id)
ChatController.delete: Future<bool>(String id)
ChatController.dispose: void()
ChatPerson: final class
ChatPerson.new: const new({required String id, required String name, required String initials})
ChatPerson.id: get String
ChatPerson.name: get String
ChatPerson.initials: get String
ChatDelivery: enum {sending, sent, read, failed}
ChatDelivery.sending: static final ChatDelivery
ChatDelivery.sent: static final ChatDelivery
ChatDelivery.read: static final ChatDelivery
ChatDelivery.failed: static final ChatDelivery
ChatQuote: final class
ChatQuote.new: const new({required String messageId, required String text, ChatPerson? author})
ChatQuote.messageId: get String
ChatQuote.text: get String
ChatQuote.author: get ChatPerson?
ChatMessage: sealed class permits {ReceivedMessage, SentMessage}
ChatMessage.new: const new({required String id, required String text, required DateTime at, ChatQuote? replyTo})
ChatMessage.id: get String
ChatMessage.text: get String
ChatMessage.at: get DateTime
ChatMessage.replyTo: get ChatQuote?
ChatMessage.quote: abstract get ChatQuote
ReceivedMessage: final class extends ChatMessage
ReceivedMessage.new: const new({required super.id, required ChatPerson author, required super.text, required super.at, super.replyTo})
ReceivedMessage.author: get ChatPerson
ReceivedMessage.quote: get ChatQuote
SentMessage: final class extends ChatMessage
SentMessage.new: const new({required super.id, required super.text, required super.at, required ChatDelivery delivery, super.replyTo})
SentMessage.delivery: get ChatDelivery
SentMessage.quote: get ChatQuote
SentMessage.copyWith: SentMessage({ChatDelivery? delivery, String? id, DateTime? at})
ChatPage: final class
ChatPage.new: const new({required List<ChatMessage> messages, required bool hasOlder})
ChatPage.messages: get List<ChatMessage>
ChatPage.hasOlder: get bool
ChatDraft: final class
ChatDraft.new: const new({required String localId, required String text, ChatQuote? replyTo})
ChatDraft.localId: get String
ChatDraft.text: get String
ChatDraft.replyTo: get ChatQuote?
ChatEvent: sealed class permits {ChatMessageArrived, ChatReadUntil, ChatTyping}
ChatEvent.new: const new()
ChatMessageArrived: final class extends ChatEvent
ChatMessageArrived.new: const new(ReceivedMessage message)
ChatMessageArrived.message: get ReceivedMessage
ChatTyping: final class extends ChatEvent
ChatTyping.new: const new(List<ChatPerson> people)
ChatTyping.people: get List<ChatPerson>
ChatReadUntil: final class extends ChatEvent
ChatReadUntil.new: const new(DateTime until)
ChatReadUntil.until: get DateTime
ChatRow: sealed class permits {ChatDayRow, ChatMessageRow}
ChatRow.new: const new()
ChatDayRow: final class extends ChatRow
ChatDayRow.new: const new(DateTime day)
ChatDayRow.day: get DateTime
ChatMessageRow: final class extends ChatRow
ChatMessageRow.new: const new(ChatMessage message, {required bool opens, required bool closes})
ChatMessageRow.message: get ChatMessage
ChatMessageRow.opens: get bool
ChatMessageRow.closes: get bool
layoutChat: List<ChatRow>(List<ChatMessage> messages)
ChatNoNetwork: final class implements Exception
ChatNoNetwork.new: const new()
ChatService: abstract interface class
ChatService.new: new()
ChatService.load: abstract Future<ChatPage>()
ChatService.loadOlder: abstract Future<ChatPage>(String beforeId)
ChatService.send: abstract Future<SentMessage>(ChatDraft draft)
ChatService.delete: abstract Future<void>(String id)
ChatService.events: abstract get Stream<ChatEvent>
sampleChatNow: get dynamic
sampleChatPerson: const dynamic
sampleChatMessages: get dynamic
sampleChatSending: get dynamic
SampleChatService: final class implements ChatService
SampleChatService.new: new({List<ChatMessage>? messages, Duration latency, DateTime Function() clock})
SampleChatService.latency: get Duration
SampleChatService.clock: get DateTime Function()
SampleChatService.load: Future<ChatPage>()
SampleChatService.loadOlder: Future<ChatPage>(String beforeId)
SampleChatService.send: Future<SentMessage>(ChatDraft draft)
SampleChatService.delete: Future<void>(String id)
SampleChatService.events: get Stream<ChatEvent>
chatL10n: const Map<String, Map<String, String>>
ChatStrings: final class
ChatStrings.new: const new(String language)
ChatStrings.of: factory new(BuildContext context)
ChatStrings.language: get String
ChatStrings.back: get String
ChatStrings.you: get String
ChatStrings.message: get String
ChatStrings.attach: get String
ChatStrings.profile: String(String name)
ChatStrings.placeholder: get String
ChatStrings.send: get String
ChatStrings.delivery: String(ChatDelivery delivery)
ChatStrings.retry: get String
ChatStrings.actions: get String
ChatStrings.copy: get String
ChatStrings.reply: get String
ChatStrings.delete: get String
ChatStrings.deleteTitle: get String
ChatStrings.deleteMessage: get String
ChatStrings.cancel: get String
ChatStrings.copied: get String
ChatStrings.replyingTo: String(String name)
ChatStrings.inReplyTo: String(String name)
ChatStrings.cancelReply: get String
ChatStrings.typing: String(List<ChatPerson> people)
ChatStrings.arrived: String(ReceivedMessage message)
ChatStrings.time: String(DateTime at)
ChatStrings.day: String(DateTime day, DateTime now)
ChatStrings.dayLabel: String(DateTime day, DateTime now)
ChatStrings.loading: get String
ChatStrings.loadingOlder: get String
ChatStrings.olderFailed: get String
ChatStrings.emptyTitle: get String
ChatStrings.emptyMessage: get String
ChatStrings.errorTitle: get String
ChatStrings.errorMessage: get String
ChatStrings.offlineTitle: get String
ChatStrings.offlineMessage: get String
ChatStrings.notice: String(ChatNotice notice)

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
chatBackBackRetourرجوع
chatYouYouVousأنت
chatMessageMessageMessageرسالة
chatAttachAdd an attachmentAjouter une pièce jointeإضافة مرفق
chatProfileProfile of {name}Profil de {name}الملف الشخصي لـ {name}
chatPlaceholderMessageMessageاكتب رسالة
chatSendSendEnvoyerإرسال
chatSendingSending…Envoi…جارٍ الإرسال…
chatSentSentEnvoyéأُرسلت
chatReadReadLuقُرئت
chatFailedNot sentNon envoyéلم تُرسل
chatRetryTry againRéessayerأعد المحاولة
chatActionsActionsActionsإجراءات
chatCopyCopyCopierنسخ
chatReplyReplyRépondreرد
chatDeleteDeleteSupprimerحذف
chatDeleteTitleDelete this message?Supprimer ce message ?حذف هذه الرسالة؟
chatDeleteMessageIt will be removed from this conversation. This can’t be undone.Il sera retiré de la discussion. Cette action est définitive.ستُزال من هذه المحادثة. لا يمكن التراجع عن ذلك.
chatCancelCancelAnnulerإلغاء
chatCopiedMessage copiedMessage copiéتم نسخ الرسالة
chatReplyingToReplying to {name}Réponse à {name}رد على {name}
chatInReplyToIn reply to {name}En réponse à {name}ردًا على {name}
chatCancelReplyCancel replyNe pas répondreإلغاء الرد
chatTyping{name} is typing…{name} écrit…{name} يكتب…
chatTypingManySeveral people are typing…Plusieurs personnes écrivent…عدة أشخاص يكتبون…
chatArrivedNew message from {name}: {text}Nouveau message de {name} : {text}رسالة جديدة من {name}: {text}
chatTodayTodayAujourd’huiاليوم
chatYesterdayYesterdayHierأمس
chatMondayMondayLundiالاثنين
chatTuesdayTuesdayMardiالثلاثاء
chatWednesdayWednesdayMercrediالأربعاء
chatThursdayThursdayJeudiالخميس
chatFridayFridayVendrediالجمعة
chatSaturdaySaturdaySamediالسبت
chatSundaySundayDimancheالأحد
chatDate{month}/{day}/{year}{day}/{month}/{year}{day}/{month}/{year}
chatLoadingLoading messagesChargement des messagesجارٍ تحميل الرسائل
chatLoadingOlderLoading earlier messagesChargement des messages précédentsجارٍ تحميل الرسائل السابقة
chatOlderFailedEarlier messages could not load.Impossible de charger les messages précédents.تعذّر تحميل الرسائل السابقة.
chatEmptyTitleNo messages yetAucun messageلا توجد رسائل
chatEmptyMessageWrite the first message below.Écrivez le premier message ci-dessous.اكتب الرسالة الأولى في الأسفل.
chatErrorTitleThis conversation could not loadImpossible de charger la discussionتعذّر تحميل المحادثة
chatErrorMessageSomething went wrong on our side.Un problème est survenu de notre côté.حدثت مشكلة من جهتنا.
chatOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
chatOfflineMessageCheck your connection, then try again. Your draft is kept.Vérifiez votre connexion, puis réessayez. Votre brouillon est gardé.تحقق من اتصالك ثم أعد المحاولة. مسودتك محفوظة.
chatSendOfflineYou are offline. Tap “Try again” on a message to resend it.Vous êtes hors ligne. Touchez « Réessayer » sur un message pour le renvoyer.أنت غير متصل. اضغط «أعد المحاولة» على الرسالة لإعادة إرسالها.
chatSendFailedA message was not sent. Tap “Try again” to resend it.Un message n’est pas parti. Touchez « Réessayer » pour le renvoyer.لم تُرسل إحدى الرسائل. اضغط «أعد المحاولة» لإعادة إرسالها.
chatDeleteFailedThe message could not be deleted.Le message n’a pas pu être supprimé.تعذّر حذف الرسالة.

Reference renders

The 16 images published with version 0.2.4, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify chat compares your copy to them.

iOS · Light
Chat thread, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Chat thread, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Chat thread, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Chat thread, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Chat thread, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Chat thread, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Chat thread, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Chat thread, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Chat thread, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Chat thread, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Chat thread, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Chat thread, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Chat thread, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Chat thread, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Chat thread, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Chat thread, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • show the message history of a one-to-one or group chat, day by day
  • send a message that shows at once then turns sent or read
  • keep messages in order when several are sent in a row
  • show a typing indicator while the other person writes
  • show the quoted message above a reply
Not for
  • the list of all discussions: use the conversations list (conversations)
  • a comment thread under some content: compose message bubbles (message-bubble)
States
  • Thread
  • Sending
  • Loading
  • Empty
  • Error
  • Offline

Arrows to move, Enter to open, Escape to close.