fixing #321
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run

This commit is contained in:
otsmr 2025-12-07 01:56:42 +01:00
parent a63343ccdd
commit 004f00f308
3 changed files with 270 additions and 2 deletions

View file

@ -251,7 +251,8 @@ class _MessageInputState extends State<MessageInput> {
width: (100 - _cancelSlideOffset) % 101,
),
Text(
context.lang.voiceMessageSlideToCancel),
context.lang.voiceMessageSlideToCancel,
),
] else ...[
Expanded(
child: Container(),
@ -265,7 +266,7 @@ class _MessageInputState extends State<MessageInput> {
),
),
),
const SizedBox(width: 20)
const SizedBox(width: 20),
],
],
)

View file

@ -22,6 +22,7 @@ import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
import 'package:twonly/src/views/chats/media_viewer_components/reaction_buttons.component.dart';
import 'package:twonly/src/views/components/animate_icon.dart';
import 'package:twonly/src/views/components/loader.dart';
import 'package:twonly/src/views/components/media_view_sizing.dart';
import 'package:video_player/video_player.dart';
@ -58,6 +59,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
bool imageSaved = false;
bool imageSaving = false;
bool displayTwonlyPresent = false;
bool _showDownloadingLoader = false;
late String _currentMediaSender;
final emojiKey = GlobalKey<EmojiFloatWidgetState>();
@ -183,6 +185,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
downloadStateListener = stream.listen((updated) async {
if (updated == null) return;
if (updated.downloadState != DownloadState.ready) {
setState(() {
_showDownloadingLoader = true;
});
if (!downloadTriggered) {
downloadTriggered = true;
final mediaFile = await twonlyDB.mediaFilesDao
@ -204,6 +209,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
bool showTwonly,
) async {
if (allMediaFiles.isEmpty) return;
setState(() {
_showDownloadingLoader = false;
});
final currentMediaLocal =
await MediaFileService.fromMediaId(allMediaFiles.first.mediaId!);
if (currentMediaLocal == null || !mounted) return;
@ -503,6 +511,17 @@ class _MediaViewerViewState extends State<MediaViewerView> {
child: Stack(
fit: StackFit.expand,
children: [
if (_showDownloadingLoader)
Center(
child: SizedBox(
height: 60,
width: 60,
child: ThreeRotatingDots(
size: 40,
color: context.color.primary,
),
),
),
if ((currentMedia != null || videoController != null) &&
(canBeSeenUntil == null || progress >= 0))
GestureDetector(

View file

@ -0,0 +1,248 @@
// Credits: https://github.com/watery-desert/loading_animation_widget/tree/main/lib/src/three_rotating_dots
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
extension LoadingAnimationControllerX on AnimationController {
T eval<T>(Tween<T> tween, {Curve curve = Curves.linear}) =>
tween.transform(curve.transform(value));
double evalDouble({
double from = 0,
double to = 1,
double begin = 0,
double end = 1,
Curve curve = Curves.linear,
}) {
return eval(
Tween<double>(begin: from, end: to),
curve: Interval(begin, end, curve: curve),
);
}
}
class ThreeRotatingDots extends StatefulWidget {
const ThreeRotatingDots({
required this.color,
required this.size,
super.key,
});
final double size;
final Color color;
@override
State<ThreeRotatingDots> createState() => _ThreeRotatingDotsState();
}
class _ThreeRotatingDotsState extends State<ThreeRotatingDots>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
)..repeat();
}
@override
Widget build(BuildContext context) {
final color = widget.color;
final size = widget.size;
final dotSize = size / 3;
final edgeOffset = (size - dotSize) / 2;
const firstDotsInterval = Interval(
0,
0.50,
curve: Curves.easeInOutCubic,
);
const secondDotsInterval = Interval(
0.50,
1,
curve: Curves.easeInOutCubic,
);
return SizedBox(
width: size,
height: size,
child: AnimatedBuilder(
animation: _animationController,
builder: (_, __) => Transform.translate(
offset: Offset(0, size / 12),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
_BuildDot.first(
color: color,
size: dotSize,
controller: _animationController,
dotOffset: edgeOffset,
beginAngle: math.pi,
endAngle: 0,
interval: firstDotsInterval,
),
_BuildDot.first(
color: color,
size: dotSize,
controller: _animationController,
dotOffset: edgeOffset,
beginAngle: 5 * math.pi / 3,
endAngle: 2 * math.pi / 3,
interval: firstDotsInterval,
),
_BuildDot.first(
color: color,
size: dotSize,
controller: _animationController,
dotOffset: edgeOffset,
beginAngle: 7 * math.pi / 3,
endAngle: 4 * math.pi / 3,
interval: firstDotsInterval,
),
/// Next 3 dots
_BuildDot.second(
controller: _animationController,
beginAngle: 0,
endAngle: -math.pi,
interval: secondDotsInterval,
dotOffset: edgeOffset,
color: color,
size: dotSize,
),
_BuildDot.second(
controller: _animationController,
beginAngle: 2 * math.pi / 3,
endAngle: -math.pi / 3,
interval: secondDotsInterval,
dotOffset: edgeOffset,
color: color,
size: dotSize,
),
_BuildDot.second(
controller: _animationController,
beginAngle: 4 * math.pi / 3,
endAngle: math.pi / 3,
interval: secondDotsInterval,
dotOffset: edgeOffset,
color: color,
size: dotSize,
),
],
),
),
),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
class _BuildDot extends StatelessWidget {
const _BuildDot.second({
required this.controller,
required this.beginAngle,
required this.endAngle,
required this.interval,
required this.dotOffset,
required this.color,
required this.size,
}) : first = false;
const _BuildDot.first({
required this.controller,
required this.beginAngle,
required this.endAngle,
required this.interval,
required this.dotOffset,
required this.color,
required this.size,
}) : first = true;
final AnimationController controller;
final double beginAngle;
final double endAngle;
final Interval interval;
final double dotOffset;
final Color color;
final double size;
final bool first;
@override
Widget build(BuildContext context) {
return Visibility(
visible: first
? controller.value <= interval.end
: controller.value >= interval.begin,
child: Transform.rotate(
angle: controller.eval(
Tween<double>(begin: beginAngle, end: endAngle),
curve: interval,
),
child: Transform.translate(
offset: controller.eval(
Tween<Offset>(
begin: first ? Offset.zero : Offset(0, -dotOffset),
end: first ? Offset(0, -dotOffset) : Offset.zero,
),
curve: interval,
),
child: DrawDot.circular(
color: color,
dotSize: size,
),
),
),
);
}
}
class DrawDot extends StatelessWidget {
const DrawDot.circular({
required double dotSize,
required this.color,
super.key,
}) : width = dotSize,
height = dotSize,
circular = true;
const DrawDot.elliptical({
required this.width,
required this.height,
required this.color,
super.key,
}) : circular = false;
final double width;
final double height;
final bool circular;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: color,
shape: circular ? BoxShape.circle : BoxShape.rectangle,
borderRadius: circular
? null
: BorderRadius.all(Radius.elliptical(width, height)),
),
);
}
}