Fliin
On this page

Screens / messaging

Post and comments

Shows one post with its paged comments and one level of replies, a composer that posts in order without waiting, retry, and deleting your own comments after confirmation.

16 of 16 variants pass
Post and comments, loaded state
Platform
Brightness
Text scale
Direction

Import

Post and comments 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 post

Also installs: alert, app-bar, button, comment-row, composer, confirm-delete, core, empty-state, post-card, result-card, skeleton, spinner, text, menu, link-button, avatar

Examples

Loaded

A post and its comments, more of them loaded while scrolling. standalone holds its own controller over the service you pass; give it yours to reach your backend.

Post and comments, loaded state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: PostLoaded(
        samplePost,
        threads: samplePostComments.take(6).toList(),
        more: const CommentsMoreAvailable('6'),
      ),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Sending

Comments written offline wait at the top of the thread, and a notice says they will be posted once the connection is back.

Post and comments, sending state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: PostLoaded(
        samplePost,
        threads: [...samplePostPending, ...samplePostComments.take(4)],
        more: const CommentsMoreAvailable('4'),
        notice: PostNotice.sendOffline,
      ),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Loading

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

Post and comments, loading state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: const PostLoading(),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Comments error

The post is there but its comments could not load: only that part offers to try again.

Post and comments, comments-error state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: PostLoaded(samplePost, more: const CommentsMoreFailed(null, offline: true)),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Error

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

Post and comments, error state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: const PostFailed(),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Offline

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

Post and comments, offline state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: const PostOffline(),
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Closed

A post whose comments are closed: canComment: false removes the input bar and says so under "Comments"; the comments already there stay.

Post and comments, closed state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: PostLoaded(samplePost, threads: samplePostComments.take(6).toList(), more: const CommentsMoreEnd()),
      canComment: false,
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

No replies

Comments on a single level: canReply: false removes "Reply" under each comment.

Post and comments, no-replies state
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

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

  @override
  Widget build(BuildContext context) {
    return PostScreen.standalone(
      service: SamplePostService(latency: const Duration(milliseconds: 600), now: () => samplePostNow),
      postId: samplePost.id,
      me: sampleMe,
      initial: PostLoaded(
        samplePost,
        threads: samplePostComments.take(6).toList(),
        more: const CommentsMoreAvailable('6'),
      ),
      canReply: false,
      now: () => samplePostNow,
      onBack: () {},
      onAuthor: (_) {},
      onShare: (_) {},
    );
  }
}

Routing

fliin add post 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/post_route.dart, next to post_page.dart.
import 'package:go_router/go_router.dart';

import 'post_page.dart';

/// Add postRoute to the routes of your GoRouter.
final postRoute = GoRoute(
  path: '/post/:id',
  builder: (context, state) => PostPage(
    id: state.pathParameters['id']!,
    compose: state.uri.queryParameters['compose'] == 'true',
    // Alone in the stack (deep link, go): your app's home.
    onBack: () => context.canPop() ? context.pop() : context.go('/'),
    onAuthor: (author) => context.push('/profile/${Uri.encodeComponent(author.id)}'),
  ),
);

navigator

// lib/post_route.dart, next to post_page.dart.
import 'package:flutter/material.dart';

import 'post_page.dart';
import 'profile_route.dart';

/// Opens “Post and comments”: `Navigator.of(context).push(postRoute(id))`.
Route<void> postRoute(String id, {bool compose = false}) => MaterialPageRoute<void>(
  builder: (context) => PostPage(
    id: id,
    compose: compose,
    onBack: () => Navigator.of(context).maybePop(),
    onAuthor: (author) => Navigator.of(context).push(profileRoute(author.id)),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add post 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/post_page.dart
// Your backend: implement PostService (post_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';

class PostPage extends StatelessWidget {
  const PostPage({
    super.key,
    required this.id,
    this.compose = false,
    required this.onBack,
    required this.onAuthor,
  });

  final String id;
  final bool compose;
  final VoidCallback onBack;
  final ValueChanged<PostAuthor> onAuthor;

  @override
  Widget build(BuildContext context) => PostScreen.standalone(
    service: SamplePostService(),
    postId: id,
    me: sampleMe,
    compose: compose,
    onBack: onBack,
    onAuthor: onAuthor,
  );
}

provider

// lib/post_page.dart
// Your backend: implement PostService (post_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

class PostPage extends StatelessWidget {
  const PostPage({
    super.key,
    required this.id,
    this.compose = false,
    required this.onBack,
    required this.onAuthor,
  });

  final String id;
  final bool compose;
  final VoidCallback onBack;
  final ValueChanged<PostAuthor> onAuthor;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => PostController(service: SamplePostService(), postId: id, me: sampleMe)..load(),
    child: Builder(
      builder: (context) => PostScreen(
        controller: context.read<PostController>(),
        compose: compose,
        onBack: onBack,
        onAuthor: onAuthor,
      ),
    ),
  );
}

riverpod

// lib/post_page.dart
// Your backend: implement PostService (post_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

final postControllerProvider = Provider.autoDispose.family<PostController, String>((ref, id) {
  final controller = PostController(service: SamplePostService(), postId: id, me: sampleMe)..load();
  ref.onDispose(controller.dispose);
  return controller;
});

class PostPage extends ConsumerWidget {
  const PostPage({
    super.key,
    required this.id,
    this.compose = false,
    required this.onBack,
    required this.onAuthor,
  });

  final String id;
  final bool compose;
  final VoidCallback onBack;
  final ValueChanged<PostAuthor> onAuthor;

  @override
  Widget build(BuildContext context, WidgetRef ref) => PostScreen(
    controller: ref.watch(postControllerProvider(id)),
    compose: compose,
    onBack: onBack,
    onAuthor: onAuthor,
  );
}

bloc

// lib/post_page.dart
// Your backend: implement PostService (post_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/post/post_controller.dart';
import 'package:your_app/ui/fliin/screens/post/post_data.dart';
import 'package:your_app/ui/fliin/screens/post/post_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 PostCubit extends Cubit<({PostState state, int version})> {
  PostCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final PostController controller;

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

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

class PostPage extends StatelessWidget {
  const PostPage({
    super.key,
    required this.id,
    this.compose = false,
    required this.onBack,
    required this.onAuthor,
  });

  final String id;
  final bool compose;
  final VoidCallback onBack;
  final ValueChanged<PostAuthor> onAuthor;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => PostCubit(PostController(service: SamplePostService(), postId: id, me: sampleMe)..load()),
    child: Builder(
      builder: (context) => PostScreen(
        controller: context.read<PostCubit>().controller,
        compose: compose,
        onBack: onBack,
        onAuthor: onAuthor,
      ),
    ),
  );
}

The snippets lead to profile: install these screens too and paste their snippets.

API

PostScreen: class extends StatefulWidget
PostScreen.new: const new({super.key, required PostController controller, bool compose, VoidCallback? onBack, ValueChanged<PostAuthor>? onAuthor, ValueChanged<PostDetail>? onShare, List<FlMenuEntry> Function(PostDetail post)? menu, DateTime Function() now, bool canComment, bool canReply})
PostScreen.standalone: const new({super.key, required PostService service, required String postId, required PostAuthor me, PostState? initial, bool compose, VoidCallback? onBack, ValueChanged<PostAuthor>? onAuthor, ValueChanged<PostDetail>? onShare, List<FlMenuEntry> Function(PostDetail post)? menu, DateTime Function() now, bool canComment, bool canReply})
PostScreen.controller: get PostController?
PostScreen.service: get PostService?
PostScreen.postId: get String?
PostScreen.me: get PostAuthor?
PostScreen.initial: get PostState?
PostScreen.compose: get bool
PostScreen.onBack: get VoidCallback?
PostScreen.onAuthor: get ValueChanged<PostAuthor>?
PostScreen.onShare: get ValueChanged<PostDetail>?
PostScreen.menu: get List<FlMenuEntry> Function(PostDetail post)?
PostScreen.now: get DateTime Function()
PostScreen.canComment: get bool
PostScreen.canReply: get bool
PostScreen.createState: State<PostScreen>()
PostState: sealed class permits {PostFailed, PostLoaded, PostLoading, PostOffline}
PostState.new: const new()
PostLoading: final class extends PostState
PostLoading.new: const new()
PostFailed: final class extends PostState
PostFailed.new: const new()
PostOffline: final class extends PostState
PostOffline.new: const new()
CommentsMore: sealed class permits {CommentsMoreAvailable, CommentsMoreEnd, CommentsMoreFailed, CommentsMoreLoading}
CommentsMore.new: const new()
CommentsMoreAvailable: final class extends CommentsMore
CommentsMoreAvailable.new: const new(String? after)
CommentsMoreAvailable.after: get String?
CommentsMoreLoading: final class extends CommentsMore
CommentsMoreLoading.new: const new(String? after)
CommentsMoreLoading.after: get String?
CommentsMoreFailed: final class extends CommentsMore
CommentsMoreFailed.new: const new(String? after, {required bool offline})
CommentsMoreFailed.after: get String?
CommentsMoreFailed.offline: get bool
CommentsMoreEnd: final class extends CommentsMore
CommentsMoreEnd.new: const new()
PostNotice: enum {sendOffline, sendFailed, deleteFailed, likeFailed}
PostNotice.sendOffline: static final PostNotice
PostNotice.sendFailed: static final PostNotice
PostNotice.deleteFailed: static final PostNotice
PostNotice.likeFailed: static final PostNotice
PostLoaded: final class extends PostState
PostLoaded.new: const new(PostDetail post, {List<CommentThread> threads, CommentsMore more, PostNotice? notice})
PostLoaded.post: get PostDetail
PostLoaded.threads: get List<CommentThread>
PostLoaded.more: get CommentsMore
PostLoaded.notice: get PostNotice?
PostController: class extends ChangeNotifier
PostController.new: new({required PostService service, required String postId, required PostAuthor me, PostState initial, DateTime Function() clock, String Function()? newLocalId})
PostController.service: get PostService
PostController.postId: get String
PostController.me: get PostAuthor
PostController.clock: get DateTime Function()
PostController.newLocalId: get String Function()
PostController.state: get PostState
PostController.load: Future<void>()
PostController.loadMore: Future<bool>()
PostController.toggleLike: bool()
PostController.send: Future<bool>(String text, {String? replyTo})
PostController.retry: Future<bool>(String id)
PostController.canDelete: bool(String id)
PostController.delete: Future<bool>(String id)
PostController.dispose: void()
PostAuthor: final class
PostAuthor.new: const new({required String id, required String name, required String initials, ImageProvider? avatar, FlAvatarColor color})
PostAuthor.id: get String
PostAuthor.name: get String
PostAuthor.initials: get String
PostAuthor.avatar: get ImageProvider?
PostAuthor.color: get FlAvatarColor
PostDetail: final class
PostDetail.new: const new({required String id, required PostAuthor author, required DateTime at, String text, List<ImageProvider> images, int likes, bool liked, int comments})
PostDetail.id: get String
PostDetail.author: get PostAuthor
PostDetail.at: get DateTime
PostDetail.text: get String
PostDetail.images: get List<ImageProvider>
PostDetail.likes: get int
PostDetail.liked: get bool
PostDetail.comments: get int
PostDetail.withLiked: PostDetail(bool liked)
PostDetail.withComments: PostDetail(int delta)
CommentDelivery: enum {sent, sending, failed}
CommentDelivery.sent: static final CommentDelivery
CommentDelivery.sending: static final CommentDelivery
CommentDelivery.failed: static final CommentDelivery
PostComment: final class
PostComment.new: const new({required String id, required PostAuthor author, required DateTime at, required String text, bool mine, CommentDelivery delivery})
PostComment.id: get String
PostComment.author: get PostAuthor
PostComment.at: get DateTime
PostComment.text: get String
PostComment.mine: get bool
PostComment.delivery: get CommentDelivery
PostComment.copyWith: PostComment({CommentDelivery? delivery, DateTime? at})
CommentThread: final class
CommentThread.new: const new(PostComment comment, {List<PostComment> replies})
CommentThread.comment: get PostComment
CommentThread.replies: get List<PostComment>
CommentPage: final class
CommentPage.new: const new(List<CommentThread> threads, {String? next})
CommentPage.threads: get List<CommentThread>
CommentPage.next: get String?
CommentDraft: final class
CommentDraft.new: const new({required String localId, required String postId, required String text, String? replyTo})
CommentDraft.localId: get String
CommentDraft.postId: get String
CommentDraft.text: get String
CommentDraft.replyTo: get String?
PostNoNetwork: final class implements Exception
PostNoNetwork.new: const new()
PostNoNetwork.toString: String()
PostService: abstract interface class
PostService.new: new()
PostService.load: abstract Future<PostDetail>(String postId)
PostService.comments: abstract Future<CommentPage>(String postId, {String? after})
PostService.setLiked: abstract Future<void>(String postId, bool liked)
PostService.send: abstract Future<PostComment>(CommentDraft draft)
PostService.delete: abstract Future<void>(String commentId)
sampleMe: const dynamic
samplePostNow: get DateTime
samplePost: get PostDetail
samplePostComments: get List<CommentThread>
samplePostPending: get List<CommentThread>
SamplePostService: final class implements PostService
SamplePostService.new: new({PostDetail? post, List<CommentThread>? threads, int pageSize, Duration latency, DateTime Function()? now})
SamplePostService.pageSize: get int
SamplePostService.latency: get Duration
SamplePostService.now: get DateTime Function()?
SamplePostService.load: Future<PostDetail>(String postId)
SamplePostService.comments: Future<CommentPage>(String postId, {String? after})
SamplePostService.setLiked: Future<void>(String postId, bool liked)
SamplePostService.send: Future<PostComment>(CommentDraft draft)
SamplePostService.delete: Future<void>(String commentId)
postL10n: const Map<String, Map<String, String>>
PostStrings: final class
PostStrings.new: const new(String language)
PostStrings.of: factory new(BuildContext context)
PostStrings.language: get String
PostStrings.title: get String
PostStrings.back: get String
PostStrings.more: get String
PostStrings.loading: get String
PostStrings.like: get String
PostStrings.liked: String(bool liked)
PostStrings.comment: get String
PostStrings.share: get String
PostStrings.author: String(String name)
PostStrings.likes: String(int count)
PostStrings.comments: String(int count)
PostStrings.photos: String(int count)
PostStrings.commentsTitle: get String
PostStrings.reply: get String
PostStrings.replyTo: String(String name)
PostStrings.replying: String(String name)
PostStrings.inReplyTo: String(String name)
PostStrings.cancelReply: get String
PostStrings.delete: get String
PostStrings.deleteComment: String(String text)
PostStrings.deleteTitle: get String
PostStrings.deleteMessage: get String
PostStrings.cancel: get String
PostStrings.deleted: get String
PostStrings.retry: get String
PostStrings.sending: get String
PostStrings.notSent: get String
PostStrings.message: get String
PostStrings.placeholder: get String
PostStrings.send: get String
PostStrings.sendOffline: get String
PostStrings.sendFailed: get String
PostStrings.deleteFailed: get String
PostStrings.likeFailed: get String
PostStrings.noComments: get String
PostStrings.commentsClosed: get String
PostStrings.loadingComments: get String
PostStrings.commentsFailed: get String
PostStrings.commentsOffline: get String
PostStrings.errorTitle: get String
PostStrings.errorMessage: get String
PostStrings.offlineTitle: get String
PostStrings.offlineMessage: get String
PostStrings.format: get FlCountFormat
PostStrings.count: FlPostCount(int count, String Function(int count) read)
PostStrings.age: String(FlPostAge age)

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
postTitlePostPublicationمنشور
postBackBackRetourرجوع
postMoreMore actionsPlus d’actionsمزيد من الإجراءات
postLoadingLoading the postChargement de la publicationجارٍ تحميل المنشور
postLikeLikeJ’aimeإعجاب
postLikedLikedJ’aime ajoutéتم الإعجاب
postUnlikedLike removedJ’aime retiréأُلغي الإعجاب
postCommentCommentCommenterتعليق
postShareSharePartagerمشاركة
postAuthorOpen the profile of {name}Voir le profil de {name}عرض الملف الشخصي لـ {name}
postLikesOne{count} like{count} J’aimeإعجاب واحد
postLikesMany{count} likes{count} J’aimeالإعجابات: {count}
postCommentsOne{count} comment{count} commentaireتعليق واحد
postCommentsMany{count} comments{count} commentairesالتعليقات: {count}
postPhotosOne{count} photo{count} photoصورة واحدة
postPhotosMany{count} photos{count} photosالصور: {count}
postCommentsTitleCommentsCommentairesالتعليقات
postReplyReplyRépondreرد
postReplyToReply to {name}Répondre à {name}رد على {name}
postReplyingReplying to {name}Réponse à {name}رد على {name}
postInReplyToIn reply to {name}En réponse à {name}ردًا على {name}
postCancelReplyDon’t replyNe pas répondreإلغاء الرد
postDeleteDeleteSupprimerحذف
postDeleteCommentDelete your comment “{text}”Supprimer votre commentaire « {text} »حذف تعليقك «{text}»
postDeleteTitleDelete this comment?Supprimer ce commentaire ?حذف هذا التعليق؟
postDeleteMessageThe comment and its replies will be erased for everyone.Le commentaire et ses réponses seront effacés pour tout le monde.سيُمحى التعليق وردوده لدى الجميع.
postCancelCancelAnnulerإلغاء
postDeletedComment deletedCommentaire suppriméحُذف التعليق
postRetryTry againRéessayerأعد المحاولة
postSendingPosting…Publication…جارٍ النشر…
postNotSentNot postedNon publiéلم يُنشر
postMessageCommentCommentaireتعليق
postPlaceholderAdd a commentAjouter un commentaireأضف تعليقًا
postSendPostPublierنشر
postSendOfflineYou are offline. Your comment is waiting: try again.Vous êtes hors ligne. Votre commentaire attend : réessayez.أنت غير متصل. تعليقك ينتظر: أعد المحاولة.
postSendFailedYour comment wasn’t posted. Try again.Votre commentaire n’a pas été publié. Réessayez.لم يُنشر تعليقك. أعد المحاولة.
postDeleteFailedCouldn’t delete the comment. It’s back.Impossible de supprimer le commentaire. Il est revenu.تعذّر حذف التعليق. لقد عاد.
postLikeFailedYour like wasn’t saved.Votre J’aime n’a pas été enregistré.لم يُحفظ إعجابك.
postNoCommentsNo comments yet. Be the first.Aucun commentaire pour l’instant. Lancez la conversation.لا تعليقات بعد. كن أول من يعلّق.
postCommentsClosedComments are turned off.Les commentaires sont désactivés.التعليقات معطّلة.
postLoadingCommentsLoading commentsChargement des commentairesجارٍ تحميل التعليقات
postCommentsFailedComments couldn’t load.Les commentaires n’ont pas pu se charger.تعذّر تحميل التعليقات.
postCommentsOfflineYou are offline. Comments can’t load.Vous êtes hors ligne. Les commentaires ne peuvent pas se charger.أنت غير متصل. لا يمكن تحميل التعليقات.
postErrorTitleThis post could not loadImpossible de charger cette publicationتعذّر تحميل هذا المنشور
postErrorMessageSomething went wrong on our side.Un problème est survenu de notre côté.حدثت مشكلة من جهتنا.
postOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
postOfflineMessageCheck your connection, then try again.Vérifiez votre connexion, puis réessayez.تحقق من اتصالك ثم أعد المحاولة.
postNowNowÀ l’instantالآن
postMinutes{count} min{count} min{count} د
postHours{count} h{count} h{count} س
postDays{count} d{count} j{count} ي
postDate{month}/{day}/{year}{day}/{month}/{year}{day}/{month}/{year}
postDecimal.,٫
postGroup, ٬
postThousandsK k ألف
postMillionsM M مليون
postBillionsB Md مليار
postDigits01234567890123456789٠١٢٣٤٥٦٧٨٩

Reference renders

The 16 images published with version 0.2.5, 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 post compares your copy to them.

iOS · Light
Post and comments, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Post and comments, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Post and comments, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Post and comments, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Post and comments, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Post and comments, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Post and comments, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Post and comments, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Post and comments, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Post and comments, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Post and comments, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Post and comments, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Post and comments, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Post and comments, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Post and comments, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Post and comments, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • show one post with the comments people left on it
  • let people reply to one another in the comments of a post
  • send a comment that shows up at once
  • delete your own comment after confirmation
  • keep loading older comments when scrolling down
Not for
  • a private conversation: use the chat thread (chat)
  • a list of posts: use the social feed (feed)
  • a single comment row: use a comment row (comment-row)
States
  • Loaded
  • Sending
  • Loading
  • Comments error
  • Error
  • Offline

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