mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 07:48:40 +00:00
fixing #321
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
Some checks are pending
Flutter analyze & test / flutter_analyze_and_test (push) Waiting to run
This commit is contained in:
parent
a63343ccdd
commit
004f00f308
3 changed files with 270 additions and 2 deletions
|
|
@ -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),
|
||||
],
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
248
lib/src/views/components/loader.dart
Normal file
248
lib/src/views/components/loader.dart
Normal 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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue