mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
571 lines
18 KiB
Dart
571 lines
18 KiB
Dart
import 'dart:async';
|
||
import 'dart:math' as math;
|
||
import 'dart:math' show max;
|
||
|
||
import 'package:PiliPlus/utils/extension.dart';
|
||
import 'package:PiliPlus/utils/global_data.dart';
|
||
import 'package:PiliPlus/utils/storage.dart';
|
||
import 'package:PiliPlus/utils/storage_key.dart';
|
||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart' hide ContextExtensionss;
|
||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||
|
||
class PayCoinsPage extends StatefulWidget {
|
||
const PayCoinsPage({
|
||
super.key,
|
||
required this.onPayCoin,
|
||
int copyright = 1,
|
||
this.hasCoin = false,
|
||
}) : hasCopyright = copyright == 1;
|
||
|
||
final Function(int coin, bool coinWithLike) onPayCoin;
|
||
final bool hasCopyright;
|
||
final bool hasCoin;
|
||
|
||
@override
|
||
State<PayCoinsPage> createState() => _PayCoinsPageState();
|
||
|
||
static void toPayCoinsPage({
|
||
required Function(int coin, bool coinWithLike) onPayCoin,
|
||
int copyright = 1,
|
||
bool hasCoin = false,
|
||
}) {
|
||
Navigator.of(Get.context!).push(
|
||
GetDialogRoute(
|
||
pageBuilder: (buildContext, animation, secondaryAnimation) {
|
||
return PayCoinsPage(
|
||
onPayCoin: onPayCoin,
|
||
copyright: copyright,
|
||
hasCoin: hasCoin,
|
||
);
|
||
},
|
||
transitionDuration: const Duration(milliseconds: 225),
|
||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||
return FadeTransition(
|
||
opacity: animation.drive(
|
||
Tween<double>(
|
||
begin: 0.0,
|
||
end: 1.0,
|
||
).chain(CurveTween(curve: Curves.linear)),
|
||
),
|
||
child: child,
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _PayCoinsPageState extends State<PayCoinsPage>
|
||
with TickerProviderStateMixin {
|
||
late final _key = GlobalKey();
|
||
late final _hasCopyright = widget.hasCopyright;
|
||
late bool _isPaying = false;
|
||
PageController? _controller;
|
||
late final RxBool _coinWithLike = Pref.coinWithLike.obs;
|
||
late final RxInt _pageIndex = 0.obs;
|
||
|
||
late final AnimationController _slide22Controller;
|
||
late final Animation<Offset> _slide22Anim;
|
||
late final AnimationController _scale22Controller;
|
||
late final Animation<double> _scale22Anim;
|
||
late final AnimationController _coinSlideController;
|
||
late final Animation<Offset> _coinSlideAnim;
|
||
late final AnimationController _coinFadeController;
|
||
late final Animation<double> _coinFadeAnim;
|
||
late final AnimationController _boxAnimController;
|
||
late final Animation<Offset> _boxAnim;
|
||
|
||
Timer? _timer;
|
||
late final RxInt _thunderIndex = (-1).obs;
|
||
late final List<String> _thunderImages = const [
|
||
'assets/images/paycoins/ic_thunder_1.png',
|
||
'assets/images/paycoins/ic_thunder_2.png',
|
||
'assets/images/paycoins/ic_thunder_3.png',
|
||
];
|
||
void _cancelTimer() {
|
||
_timer?.cancel();
|
||
_timer = null;
|
||
}
|
||
|
||
final num? _coins = GlobalData().coins;
|
||
late final List<bool> _payState;
|
||
late final List<String> _payImg;
|
||
late final List<Color> _payFilter;
|
||
|
||
bool _canPay(int index) {
|
||
if (index == 1 && widget.hasCoin) {
|
||
return false;
|
||
}
|
||
if (_coins == null) {
|
||
return true;
|
||
}
|
||
if (index == 0 && _coins >= 1) {
|
||
return true;
|
||
}
|
||
if (index == 1 && _coins >= 2) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
String _getPayImage(int index) {
|
||
if (!_payState[index]) {
|
||
return 'assets/images/paycoins/ic_22_not_enough_pay.png';
|
||
}
|
||
return index == 0
|
||
? 'assets/images/paycoins/ic_22_mario.png'
|
||
: 'assets/images/paycoins/ic_22_gun_sister.png';
|
||
}
|
||
|
||
late final color = Colors.black.withValues(alpha: 0.4);
|
||
Color _getPayFilter(int index) {
|
||
if (index == 1 && widget.hasCoin) {
|
||
return color;
|
||
}
|
||
if (_coins == null) {
|
||
return Colors.transparent;
|
||
}
|
||
if (index == 0 && _coins == 0) {
|
||
return color;
|
||
}
|
||
if (index == 1 && _coins < 2) {
|
||
return color;
|
||
}
|
||
return Colors.transparent;
|
||
}
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
if (_hasCopyright) {
|
||
_controller = PageController(viewportFraction: 0.30);
|
||
}
|
||
final count = _hasCopyright ? 2 : 1;
|
||
_payState = List.generate(count, _canPay);
|
||
_payImg = List.generate(count, _getPayImage);
|
||
_payFilter = List.generate(count, _getPayFilter);
|
||
|
||
_slide22Controller = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 50),
|
||
);
|
||
_slide22Anim = _slide22Controller.drive(
|
||
Tween(
|
||
begin: Offset.zero,
|
||
end: const Offset(0.0, -0.2),
|
||
),
|
||
);
|
||
_scale22Controller = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 50),
|
||
);
|
||
_scale22Anim = _scale22Controller.drive(
|
||
Tween(begin: 1, end: 1.1),
|
||
);
|
||
_coinSlideController = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 200),
|
||
);
|
||
_coinSlideAnim = _coinSlideController.drive(
|
||
Tween(
|
||
begin: Offset.zero,
|
||
end: const Offset(0.0, -2),
|
||
),
|
||
);
|
||
_coinFadeController = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 100),
|
||
);
|
||
_coinFadeAnim = Tween<double>(
|
||
begin: 1,
|
||
end: 0,
|
||
).animate(_coinFadeController);
|
||
_boxAnimController = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 50),
|
||
);
|
||
_boxAnim = _boxAnimController.drive(
|
||
Tween(
|
||
begin: Offset.zero,
|
||
end: const Offset(0.0, -0.2),
|
||
),
|
||
);
|
||
|
||
_scale();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_cancelTimer();
|
||
_slide22Controller.dispose();
|
||
_scale22Controller.dispose();
|
||
_coinSlideController.dispose();
|
||
_coinFadeController.dispose();
|
||
_boxAnimController.dispose();
|
||
_controller?.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
void _scale() {
|
||
_scale22Controller.forward().whenComplete(_scale22Controller.reverse);
|
||
}
|
||
|
||
void _onScroll(int index) {
|
||
_controller?.animateToPage(
|
||
index,
|
||
duration: const Duration(milliseconds: 200),
|
||
curve: Curves.ease,
|
||
);
|
||
_scale();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final size = MediaQuery.sizeOf(context);
|
||
final isPortrait = size.isPortrait;
|
||
return isPortrait
|
||
? _buildBody(isPortrait)
|
||
: Center(
|
||
child: ConstrainedBox(
|
||
constraints: BoxConstraints(
|
||
maxWidth: math.min(525, size.width * 0.6),
|
||
),
|
||
child: _buildBody(isPortrait),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildCoinWidget(int index, double factor) {
|
||
return Center(
|
||
child: SizedBox(
|
||
height: 70 + (factor * 30),
|
||
width: 70 + (factor * 30),
|
||
child: ColorFiltered(
|
||
colorFilter: ColorFilter.mode(
|
||
_payFilter[index],
|
||
BlendMode.srcATop,
|
||
),
|
||
child: Stack(
|
||
clipBehavior: Clip.none,
|
||
alignment: Alignment.center,
|
||
children: [
|
||
SlideTransition(
|
||
position: _boxAnim,
|
||
child: Image.asset(
|
||
'assets/images/paycoins/ic_pay_coins_box.png',
|
||
),
|
||
),
|
||
SlideTransition(
|
||
position: _coinSlideAnim,
|
||
child: FadeTransition(
|
||
opacity: _coinFadeAnim,
|
||
child: Image.asset(
|
||
height: 35 + (factor * 15),
|
||
width: 35 + (factor * 15),
|
||
index == 0
|
||
? 'assets/images/paycoins/ic_coins_one.png'
|
||
: 'assets/images/paycoins/ic_coins_two.png',
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _build22() {
|
||
final index = _pageIndex.value;
|
||
final canPay = _payState[index];
|
||
final payImg = _payImg[index];
|
||
return GestureDetector(
|
||
onTap: canPay ? _onPayCoin : null,
|
||
onVerticalDragStart: canPay
|
||
? (e) {
|
||
_isHorizontal = false;
|
||
_onDragDown(e);
|
||
}
|
||
: null,
|
||
onVerticalDragUpdate: canPay ? _onDragUpdate : null,
|
||
onVerticalDragEnd: canPay ? _onDragEnd : null,
|
||
onVerticalDragCancel: canPay ? _onDragEnd : null,
|
||
behavior: HitTestBehavior.opaque,
|
||
child: ScaleTransition(
|
||
scale: _scale22Anim,
|
||
child: SlideTransition(
|
||
position: _slide22Anim,
|
||
child: SizedBox(
|
||
width: 110,
|
||
height: 155,
|
||
child: Image.asset(payImg),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildBody(bool isV) => Stack(
|
||
key: _key,
|
||
clipBehavior: Clip.none,
|
||
alignment: Alignment.center,
|
||
children: [
|
||
if (_hasCopyright)
|
||
Obx(() {
|
||
final index = _thunderIndex.value;
|
||
return Offstage(
|
||
offstage: index == -1 || index == 3,
|
||
child: Image.asset(_thunderImages[index.clamp(0, 2)]),
|
||
);
|
||
}),
|
||
Align(
|
||
alignment: Alignment.bottomCenter,
|
||
child: GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
if (_hasCopyright)
|
||
Stack(
|
||
clipBehavior: Clip.none,
|
||
alignment: Alignment.center,
|
||
children: [
|
||
Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||
child: SizedBox(
|
||
height: 100,
|
||
child: PageView(
|
||
key: const PageStorageKey('PageView'),
|
||
physics: const ClampingScrollPhysics(),
|
||
controller: _controller,
|
||
onPageChanged: (index) {
|
||
_scale();
|
||
_pageIndex.value = index;
|
||
},
|
||
children: List.generate(
|
||
2,
|
||
(index) {
|
||
return ListenableBuilder(
|
||
listenable: _controller!,
|
||
builder: (context, child) {
|
||
double factor = index == 0 ? 1 : 0;
|
||
if (_controller!
|
||
.position
|
||
.hasContentDimensions) {
|
||
factor =
|
||
1 - (_controller!.page! - index).abs();
|
||
}
|
||
return Obx(
|
||
() {
|
||
if (_pageIndex.value != index &&
|
||
_isPaying) {
|
||
return const SizedBox.shrink();
|
||
}
|
||
return _buildCoinWidget(index, factor);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Obx(
|
||
() {
|
||
final index = _pageIndex.value;
|
||
if (_isPaying) {
|
||
return const SizedBox.shrink();
|
||
}
|
||
return GestureDetector(
|
||
onTap: index == 0 ? null : () => _onScroll(0),
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(left: 12),
|
||
child: Image.asset(
|
||
width: 16,
|
||
height: 28,
|
||
index == 0
|
||
? 'assets/images/paycoins/ic_left_disable.png'
|
||
: 'assets/images/paycoins/ic_left.png',
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
Align(
|
||
alignment: Alignment.centerRight,
|
||
child: Obx(() {
|
||
final index = _pageIndex.value;
|
||
if (_isPaying) {
|
||
return const SizedBox.shrink();
|
||
}
|
||
return GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onTap: index == 1 ? null : () => _onScroll(1),
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(right: 12),
|
||
child: Image.asset(
|
||
width: 16,
|
||
height: 28,
|
||
index == 1
|
||
? 'assets/images/paycoins/ic_right_disable.png'
|
||
: 'assets/images/paycoins/ic_right.png',
|
||
),
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
],
|
||
)
|
||
else
|
||
SizedBox(height: 100, child: _buildCoinWidget(0, 1)),
|
||
SizedBox(height: isV ? 25 : 10),
|
||
if (_hasCopyright)
|
||
GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onHorizontalDragStart: (e) {
|
||
_isHorizontal = true;
|
||
_onDragDown(e);
|
||
},
|
||
onHorizontalDragUpdate: _onDragUpdate,
|
||
onHorizontalDragEnd: _onDragEnd,
|
||
onHorizontalDragCancel: _onDragEnd,
|
||
child: Center(child: Obx(_build22)),
|
||
)
|
||
else
|
||
Center(child: _build22()),
|
||
if (_coins != null || widget.hasCoin) ...[
|
||
const SizedBox(height: 10),
|
||
Center(
|
||
child: Text(
|
||
'${_coins != null ? '硬币余额:${max(0.0, _coins.toDouble().toPrecision(1))}' : ''}${widget.hasCoin ? '${_coins != null ? ',' : ''}已投1枚硬币' : ''}',
|
||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||
),
|
||
),
|
||
],
|
||
const SizedBox(height: 10),
|
||
Stack(
|
||
clipBehavior: Clip.none,
|
||
alignment: Alignment.centerLeft,
|
||
children: [
|
||
GestureDetector(
|
||
onTap: () {
|
||
final newVal = !_coinWithLike.value;
|
||
_coinWithLike.value = newVal;
|
||
GStorage.setting.put(SettingBoxKey.coinWithLike, newVal);
|
||
},
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
const SizedBox(width: 12),
|
||
Obx(
|
||
() => Icon(
|
||
_coinWithLike.value
|
||
? Icons.check_box_outlined
|
||
: Icons.check_box_outline_blank,
|
||
size: 20,
|
||
color: Colors.white,
|
||
),
|
||
),
|
||
const Text(
|
||
' 同时点赞',
|
||
style: TextStyle(color: Colors.white),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
Center(
|
||
child: GestureDetector(
|
||
onTap: Get.back,
|
||
behavior: HitTestBehavior.opaque,
|
||
child: SizedBox(
|
||
width: 30,
|
||
height: 30,
|
||
child: Image.asset(
|
||
'assets/images/paycoins/ic_panel_close.png',
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
SizedBox(
|
||
height:
|
||
(isV ? 50 : 10) + MediaQuery.viewPaddingOf(context).bottom,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
|
||
late bool _isHorizontal = true;
|
||
DragStartDetails? _downPos;
|
||
void _onDragDown(DragStartDetails detail) => _downPos = detail;
|
||
void _onDragEnd([_]) => _downPos = null;
|
||
void _onDragUpdate(DragUpdateDetails e) {
|
||
if (_downPos == null) {
|
||
return;
|
||
}
|
||
final offset = _isHorizontal
|
||
? (e.localPosition.dx - _downPos!.localPosition.dx).abs()
|
||
: (e.localPosition.dy - _downPos!.localPosition.dy).abs();
|
||
if (offset < 20) {
|
||
return;
|
||
}
|
||
_downPos = null;
|
||
if (_isHorizontal) {
|
||
if (e.delta.dx > 0) {
|
||
if (_pageIndex.value == 1) {
|
||
_onScroll(0);
|
||
}
|
||
} else {
|
||
if (_pageIndex.value == 0) {
|
||
_onScroll(1);
|
||
}
|
||
}
|
||
} else {
|
||
if (e.delta.dy < 0) {
|
||
_onPayCoin();
|
||
}
|
||
}
|
||
}
|
||
|
||
void _onPayCoin() {
|
||
if (_isPaying) return;
|
||
_isPaying = true;
|
||
_pageIndex.refresh();
|
||
_slide22Controller.forward().whenComplete(() {
|
||
_slide22Controller.reverse().whenComplete(() {
|
||
if (_pageIndex.value == 1) {
|
||
_thunderIndex.value += 1;
|
||
_timer ??= Timer.periodic(const Duration(milliseconds: 50 ~/ 3), (_) {
|
||
final index = _thunderIndex.value;
|
||
if (index == _thunderImages.length) {
|
||
_cancelTimer();
|
||
} else {
|
||
_thunderIndex.value = index + 1;
|
||
}
|
||
});
|
||
}
|
||
_boxAnimController.forward().whenComplete(_boxAnimController.reverse);
|
||
_coinSlideController.forward().whenComplete(() {
|
||
_coinFadeController.forward().whenComplete(() {
|
||
Get.back();
|
||
widget.onPayCoin(_pageIndex.value + 1, _coinWithLike.value);
|
||
});
|
||
});
|
||
});
|
||
});
|
||
}
|
||
}
|