mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
190 lines
5.3 KiB
Dart
190 lines
5.3 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
class ConfirmSwiper extends StatefulWidget {
|
|
final VoidCallback onConfirmed;
|
|
final String swiperText;
|
|
|
|
const ConfirmSwiper({super.key, required this.onConfirmed, required this.swiperText});
|
|
|
|
@override
|
|
State<ConfirmSwiper> createState() => _ConfirmSwiperState();
|
|
}
|
|
|
|
class _ConfirmSwiperState extends State<ConfirmSwiper> {
|
|
double pillHorizontalPadding = 4;
|
|
double pillVerticalPadding = 2;
|
|
double pillSize = 48;
|
|
late double drag;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
setState(() {
|
|
drag = pillHorizontalPadding;
|
|
});
|
|
}
|
|
|
|
static const double _triggerThreshold = 20;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final radius = (pillSize + pillHorizontalPadding * 2) / 2;
|
|
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final areaWidth = constraints.maxWidth.isFinite
|
|
? constraints.maxWidth
|
|
: MediaQuery.of(context).size.width * 0.77;
|
|
|
|
final maxDrag = areaWidth - pillSize - pillHorizontalPadding;
|
|
final triggerAt = maxDrag - _triggerThreshold;
|
|
|
|
return GestureDetector(
|
|
onHorizontalDragUpdate: (d) {
|
|
setState(() {
|
|
drag = max(pillHorizontalPadding, min(maxDrag, drag + d.delta.dx));
|
|
});
|
|
},
|
|
onHorizontalDragEnd: (_) {
|
|
if (drag >= triggerAt) {
|
|
widget.onConfirmed();
|
|
}
|
|
setState(() => drag = pillHorizontalPadding);
|
|
},
|
|
child: SizedBox(
|
|
height: pillSize + pillHorizontalPadding * 2,
|
|
width: areaWidth,
|
|
child: Stack(
|
|
alignment: Alignment.centerLeft,
|
|
children: [
|
|
ClipPath(
|
|
clipper: TrackClipper(
|
|
cut: drag - pillHorizontalPadding,
|
|
radius: radius,
|
|
),
|
|
child: Container(
|
|
height: pillSize + pillHorizontalPadding * 2,
|
|
width: areaWidth,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surfaceContainerLowest,
|
|
borderRadius: BorderRadius.circular(99999),
|
|
),
|
|
child: Center(
|
|
child: FlowingText(
|
|
text: widget.swiperText,
|
|
opacity: 1 - (drag / maxDrag),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Positioned(
|
|
left: drag,
|
|
child: Container(
|
|
height: pillSize,
|
|
width: pillSize,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.arrow_forward,
|
|
color: Theme.of(context).colorScheme.onPrimary,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class TrackClipper extends CustomClipper<Path> {
|
|
final double cut;
|
|
final double radius;
|
|
|
|
TrackClipper({required this.cut, required this.radius});
|
|
|
|
@override
|
|
Path getClip(Size size) {
|
|
final left = cut.clamp(0.0, size.width);
|
|
final rect = Rect.fromLTRB(left, 0, size.width, size.height);
|
|
return Path()..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(radius)));
|
|
}
|
|
|
|
@override
|
|
bool shouldReclip(covariant TrackClipper oldClipper) {
|
|
return oldClipper.cut != cut;
|
|
}
|
|
}
|
|
|
|
class FlowingText extends StatefulWidget {
|
|
const FlowingText({super.key, required this.text, required this.opacity});
|
|
|
|
final String text;
|
|
final double opacity;
|
|
|
|
@override
|
|
State<FlowingText> createState() => _FlowingTextState();
|
|
}
|
|
|
|
class _FlowingTextState extends State<FlowingText> with SingleTickerProviderStateMixin {
|
|
late AnimationController controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 2400),
|
|
lowerBound: -1.0,
|
|
upperBound: 2.0)
|
|
..repeat();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Opacity(
|
|
opacity: widget.opacity,
|
|
child: AnimatedBuilder(
|
|
animation: controller,
|
|
builder: (_, __) {
|
|
return ShaderMask(
|
|
shaderCallback: (r) {
|
|
return LinearGradient(
|
|
begin: Alignment(-1 + (controller.value) * 2, 0),
|
|
end: Alignment(controller.value * 2, 0),
|
|
colors: [
|
|
Colors.transparent,
|
|
Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
Colors.transparent
|
|
],
|
|
).createShader(r);
|
|
},
|
|
blendMode: BlendMode.srcATop,
|
|
child: Text(
|
|
widget.text,
|
|
style: TextStyle(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: 16,
|
|
letterSpacing: 0.5,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|