import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import 'package:relatica/models/exec_error.dart'; import 'package:result_monad/result_monad.dart'; import '../../globals.dart'; import '../../models/timeline_entry.dart'; import '../../models/visibility.dart' as v; import '../../services/feature_version_checker.dart'; import '../../services/fediverse_server_validator.dart'; import '../../services/timeline_manager.dart'; import '../../utils/active_profile_selector.dart'; import '../../utils/snackbar_builder.dart'; class InteractionsBarControl extends StatefulWidget { final TimelineEntry entry; final bool showOpenControl; final bool isMine; const InteractionsBarControl({ super.key, required this.entry, required this.isMine, required this.showOpenControl, }); @override State createState() => _InteractionsBarControlState(); } class _InteractionsBarControlState extends State { static final _logger = Logger('$InteractionsBarControl'); var isProcessing = false; bool get isPost => widget.entry.parentId.isEmpty; bool get isFavorited => widget.entry.isFavorited; bool get youReshared => widget.entry.youReshared; int get reshares => widget.entry.engagementSummary.rebloggedCount; int get comments => widget.entry.engagementSummary.repliesCount; int get likes => widget.entry.engagementSummary.favoritesCount; Future toggleFavorited() async { setState(() { isProcessing = true; }); final newState = !isFavorited; _logger.fine('Trying to toggle favorite from $isFavorited to $newState'); final result = await getIt>() .activeEntry .andThenAsync( (tm) async => await tm.toggleFavorited(widget.entry.id, newState)); result.match(onSuccess: (update) { setState(() { _logger.fine( 'Success toggling! $isFavorited -> ${update.entry.isFavorited}'); }); }, onError: (error) { buildSnackbar(context, 'Error toggling like status: $error'); }); setState(() { isProcessing = false; }); } Future resharePost() async { setState(() { isProcessing = true; }); final fvc = getIt(); if (!fvc.canUseFeature(RelaticaFeatures.diasporaReshare)) { final serverTypeEstimate = await getIt() .getServerData(widget.entry.externalLink) .fold(onSuccess: (d) => d.softwareName, onError: (_) => ''); if (serverTypeEstimate == FediverseServiceValidator.softwareTypeDiaspora) { if (mounted) { final error = fvc.versionErrorString(RelaticaFeatures.diasporaReshare); await showConfirmDialog(context, error); } setState(() { isProcessing = false; }); } } final id = widget.entry.id; _logger.fine('Trying to reshare $id'); final result = await getIt>() .activeEntry .andThenAsync((tm) async => await tm.resharePost(id)); result.match(onSuccess: (update) { setState(() { _logger.fine('Success resharing post by ${widget.entry.author}'); }); }, onError: (error) { buildSnackbar(context, 'Error resharing post by ${widget.entry.author}'); logError(error, _logger); }); setState(() { isProcessing = false; }); } Future addComment() async { if (mounted) { context.push('/comment/new?parent_id=${widget.entry.id}'); } } Future unResharePost() async { setState(() { isProcessing = true; }); final id = widget.entry.id; _logger.fine('Trying to un-reshare $id'); final result = await getIt>() .activeEntry .andThenAsync((tm) async => await tm.unResharePost(id)); result.match(onSuccess: (update) { setState(() { _logger.fine('Success un-resharing post by ${widget.entry.author}'); }); }, onError: (error) { buildSnackbar( context, 'Error un-resharing post by ${widget.entry.author}'); logError(error, _logger); }); setState(() { isProcessing = false; }); } Widget buildButton( IconData icon, int count, bool isEnabled, String tooltip, Future Function()? onPressed, ) { return Row( children: [ IconButton( onPressed: isEnabled && !isProcessing ? onPressed : null, icon: Icon(icon), tooltip: tooltip, ), Text('$count'), ], ); } Widget buildLikeButton() { return buildButton( isFavorited ? Icons.thumb_up : Icons.thumb_up_outlined, likes, true, 'Press to toggle like/unlike', () async => await toggleFavorited(), ); } Widget buildCommentButton() { return buildButton( Icons.comment, comments, true, 'Press to add a comment', () async => await addComment(), ); } Widget buildReshareButton() { final canReshare = !widget.isMine && widget.entry.visibility.type == v.VisibilityType.public; late final String tooltip; if (canReshare) { tooltip = youReshared ? 'Press to undo reshare' : 'Press to reshare'; } else { if (widget.isMine) { tooltip = "Can't reshare your own post"; } else if (widget.entry.visibility.type != v.VisibilityType.public) { tooltip = "Can't reshare a private post"; } else { tooltip = "Can't reshare at this time"; } } return buildButton( youReshared ? Icons.repeat_on_outlined : Icons.repeat, reshares, true, tooltip, canReshare && !isProcessing ? () async => youReshared ? await unResharePost() : await resharePost() : null, ); } @override Widget build(BuildContext context) { _logger.finest('Building: ${widget.entry.toShortString()}'); return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ buildLikeButton(), buildCommentButton(), if (widget.entry.parentId.isEmpty) buildReshareButton(), ], ); } }