Files
PiliPlus/lib/pages/common/slide/common_slide_page.dart
My-Responsitories 37fb63c3b1 tweaks (#1252)
* opt: cache

* opt: MediaListPanel

* feat: nested replyreply panel

* tweaks

* opt: abstract class

* opt: PageStorageKey

* opt: contextExt

* opt: EpisodePanel

* opt

* opt: context instead GlobalKey

* feat: jump to reply

* refa: reply_reply

* fix: jump

* fix: index

* update

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* opt: keepalive

* reapply: nested replyreply

* mod: spacing

* opt: CommonSlidePageState

* fix drag bottomsheet

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* opt reply jump

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* opt reply2reply

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* tweaks

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* tweaks

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* reapply: jumpToReply

* fix: padding

* fix: anim

* fix some panels

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

* opt: implements Scaffold

* opt: remove keepalive

* revert: GlobalKey

* tweaks

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-09-15 18:45:28 +08:00

127 lines
3.2 KiB
Dart

import 'dart:math' show max;
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/gestures.dart' show PositionedGestureDetails;
import 'package:flutter/material.dart';
import 'package:get/get.dart';
abstract class CommonSlidePage extends StatefulWidget {
const CommonSlidePage({super.key, this.enableSlide = true});
final bool enableSlide;
}
mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
Offset? downPos;
bool? isSliding;
late double maxWidth;
late bool _isRTL = false;
late final bool enableSlide;
AnimationController? _animController;
Animation<Offset>? _anim;
static bool slideDismissReplyPage = Pref.slideDismissReplyPage;
@override
void initState() {
super.initState();
enableSlide = widget.enableSlide && slideDismissReplyPage;
if (enableSlide) {
_animController = AnimationController(
vsync: this,
reverseDuration: const Duration(milliseconds: 500),
);
_anim = Tween<Offset>(
begin: Offset.zero,
end: const Offset(0, 1),
).animate(_animController!);
}
}
@override
void dispose() {
_animController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return enableSlide
? LayoutBuilder(
builder: (context, constraints) {
maxWidth = constraints.maxWidth;
return SlideTransition(
position: _anim!,
child: buildPage(theme),
);
},
)
: buildPage(theme);
}
Widget buildPage(ThemeData theme);
Widget buildList(ThemeData theme) => throw UnimplementedError();
void onDismiss([_]) {
if (isSliding == true) {
final dx = downPos!.dx;
if (_animController!.value * maxWidth + (_isRTL ? (maxWidth - dx) : dx) >=
100) {
Get.back();
} else {
_animController!.reverse();
}
}
downPos = null;
isSliding = null;
}
void onPan(PositionedGestureDetails details) {
final localPosition = details.localPosition;
if (isSliding == false) {
return;
} else if (isSliding == null) {
if (downPos != null) {
Offset cumulativeDelta = localPosition - downPos!;
if (cumulativeDelta.dx.abs() >= cumulativeDelta.dy.abs()) {
downPos = localPosition;
isSliding = true;
} else {
isSliding = false;
}
} else {
isSliding = false;
}
} else if (isSliding == true) {
final from = downPos!.dx;
final to = details.localPosition.dx;
_animController!.value =
max(0, _isRTL ? from - to : to - from) / maxWidth;
}
}
void onPanDown(DragDownDetails details) {
final dx = details.localPosition.dx;
const offset = 30;
final isLTR = dx <= offset;
final isRTL = dx >= maxWidth - offset;
if (isLTR || isRTL) {
_isRTL = isRTL;
downPos = details.localPosition;
} else {
isSliding = false;
}
}
Widget slideList(ThemeData theme) => GestureDetector(
onPanDown: onPanDown,
onPanStart: onPan,
onPanUpdate: onPan,
onPanCancel: onDismiss,
onPanEnd: onDismiss,
child: buildList(theme),
);
}