338 lines
14 KiB
Dart
338 lines
14 KiB
Dart
|
import 'package:animated_text_kit/animated_text_kit.dart';
|
||
|
import 'package:dreampad/app/routes/app_pages.dart';
|
||
|
import 'package:dreampad/app/shared/constants/constants.dart';
|
||
|
import 'package:dreampad/app/shared/shared.dart';
|
||
|
import 'package:flutter/material.dart';
|
||
|
import 'package:flutter/scheduler.dart';
|
||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
|
import 'package:get/get.dart';
|
||
|
|
||
|
import '../controllers/welcome_controller.dart';
|
||
|
|
||
|
class PartnerWidget extends HookWidget {
|
||
|
const PartnerWidget({super.key});
|
||
|
|
||
|
WelcomeController get controller => Get.find<WelcomeController>();
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
final isPartnerDone = useState(false);
|
||
|
return !isPartnerDone.value
|
||
|
? buildPartner(context, isPartnerDone)
|
||
|
: buildConfirm(context);
|
||
|
}
|
||
|
|
||
|
Widget buildPartner(BuildContext context, ValueNotifier<bool> isPartnerDone) {
|
||
|
return LayoutBuilder(builder: (context, constraint) {
|
||
|
return HookBuilder(builder: (context) {
|
||
|
final isIpShown = useState(false);
|
||
|
final isDialogShown = useState(false);
|
||
|
final isTextShown = useState(false);
|
||
|
final animationController = useAnimationController(
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
lowerBound: 0,
|
||
|
upperBound: constraint.maxHeight);
|
||
|
final fadeoutAnimation = (1 -
|
||
|
useListenableSelector(
|
||
|
animationController,
|
||
|
() =>
|
||
|
animationController.value /
|
||
|
animationController.upperBound,
|
||
|
))
|
||
|
.toDouble();
|
||
|
useEffect(() {
|
||
|
void onAnimationStateChange(AnimationStatus state) {
|
||
|
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
|
||
|
if (state == AnimationStatus.completed) {
|
||
|
isPartnerDone.value = true;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
animationController.addStatusListener(onAnimationStateChange);
|
||
|
return () {
|
||
|
animationController.removeStatusListener(onAnimationStateChange);
|
||
|
};
|
||
|
}, [animationController]);
|
||
|
|
||
|
return SwipePageHandlerReceiver(
|
||
|
controller: animationController,
|
||
|
currentIndex: useState(0),
|
||
|
child: AnimatedBuilder(
|
||
|
animation: animationController,
|
||
|
builder: (context, _) => Stack(
|
||
|
children: [
|
||
|
const _Background(),
|
||
|
Positioned(
|
||
|
bottom: 5.h + animationController.value,
|
||
|
left: 452.w,
|
||
|
child: Center(
|
||
|
child: AnimatedVisibilityWidget(
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
isVisible: isTextShown.value,
|
||
|
animationWidgetBuilder:
|
||
|
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
|
||
|
child: IgnorePointer(
|
||
|
ignoring: !isTextShown.value,
|
||
|
child: SwipeNextPageHandler(
|
||
|
child: Column(
|
||
|
children: [
|
||
|
Images.up,
|
||
|
Images.welcomeBegin,
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
)),
|
||
|
Stack(
|
||
|
children: [
|
||
|
Positioned(
|
||
|
top: 42.h,
|
||
|
left: 149.w,
|
||
|
child: AnimatedVisibilityWidget(
|
||
|
isVisible: true,
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
animationWidgetBuilder:
|
||
|
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
|
||
|
isInitAnimated: true,
|
||
|
onDone: (visible) {
|
||
|
if (visible) {
|
||
|
isIpShown.value = true;
|
||
|
}
|
||
|
},
|
||
|
child: Images.ip,
|
||
|
),
|
||
|
),
|
||
|
Positioned(
|
||
|
top: 76.h,
|
||
|
left: 451.w,
|
||
|
child: Opacity(
|
||
|
opacity: fadeoutAnimation,
|
||
|
child: AnimatedVisibilityWidget(
|
||
|
isVisible: isIpShown.value,
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
animationWidgetBuilder: AnimatedVisibilityWidget
|
||
|
.fadeAnimationWidgetBuilder,
|
||
|
onDone: (visible) {
|
||
|
if (visible) {
|
||
|
isDialogShown.value = true;
|
||
|
}
|
||
|
},
|
||
|
child: Container(
|
||
|
width: 465.w,
|
||
|
height: 221.h,
|
||
|
decoration: const BoxDecoration(
|
||
|
image: DecorationImage(
|
||
|
image: Images.welcomeDialog,
|
||
|
fit: BoxFit.fill,
|
||
|
),
|
||
|
),
|
||
|
padding: REdgeInsets.only(
|
||
|
top: 45.0, left: 45.0, right: 45.0),
|
||
|
child: DefaultTextStyle(
|
||
|
style: TextStyles.mediumWhiteShadowHeight18_034,
|
||
|
child: Visibility(
|
||
|
visible: isDialogShown.value,
|
||
|
child: AnimatedTextKit(
|
||
|
totalRepeatCount: 1,
|
||
|
animatedTexts: [
|
||
|
TypewriterAnimatedText(
|
||
|
'${controller.textController.text}你好! 我是一只天马。我们天马族一直生活在这里。与探梦者为伴。接下来让我来做你探索的伙伴吧。',
|
||
|
speed: const Duration(milliseconds: 150),
|
||
|
cursor: '',
|
||
|
),
|
||
|
],
|
||
|
onFinished: () {
|
||
|
isTextShown.value = true;
|
||
|
},
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
Widget buildConfirm(BuildContext context) {
|
||
|
return HookBuilder(builder: (context) {
|
||
|
final isDialogShown = useState(false);
|
||
|
final isTextShown = useState(false);
|
||
|
return Stack(
|
||
|
children: [
|
||
|
Positioned.fill(
|
||
|
child: Container(
|
||
|
decoration: const BoxDecoration(
|
||
|
image: DecorationImage(
|
||
|
image: Images.selectBg,
|
||
|
fit: BoxFit.fill,
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
Positioned(top: 42.h, left: 149.w, child: Images.ip),
|
||
|
Positioned(
|
||
|
top: 76.h,
|
||
|
left: 451.w,
|
||
|
child: AnimatedVisibilityWidget(
|
||
|
isVisible: true,
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
animationWidgetBuilder:
|
||
|
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
|
||
|
isInitAnimated: true,
|
||
|
onDone: (visible) {
|
||
|
if (visible) {
|
||
|
isDialogShown.value = true;
|
||
|
}
|
||
|
},
|
||
|
child: Container(
|
||
|
width: 464.w,
|
||
|
height: 270.h,
|
||
|
decoration: const BoxDecoration(
|
||
|
image: DecorationImage(
|
||
|
image: Images.questionDialog,
|
||
|
fit: BoxFit.fill,
|
||
|
),
|
||
|
),
|
||
|
padding: REdgeInsets.only(top: 45.0, left: 45.0, right: 45.0),
|
||
|
child: Column(
|
||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||
|
children: [
|
||
|
Expanded(
|
||
|
child: SizedBox(
|
||
|
width: double.infinity,
|
||
|
child: Visibility(
|
||
|
visible: isDialogShown.value,
|
||
|
child: AnimatedTextKit(
|
||
|
totalRepeatCount: 1,
|
||
|
animatedTexts: [
|
||
|
TypewriterAnimatedText(
|
||
|
'每个来到建木之境的探梦者,都需要先选定自己的【梦想职业】,才能继续探索哦。准备好了吗?',
|
||
|
textStyle: TextStyles.mediumWhiteShadow18_034,
|
||
|
textAlign: TextAlign.start,
|
||
|
speed: const Duration(milliseconds: 150),
|
||
|
cursor: '',
|
||
|
),
|
||
|
],
|
||
|
onFinished: () {
|
||
|
isTextShown.value = true;
|
||
|
},
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
const RSizedBox(
|
||
|
height: 36.0,
|
||
|
),
|
||
|
AnimatedVisibilityWidget(
|
||
|
isVisible: isTextShown.value,
|
||
|
duration: const Duration(milliseconds: 500),
|
||
|
animationWidgetBuilder:
|
||
|
AnimatedVisibilityWidget.fadeAnimationWidgetBuilder,
|
||
|
child: ImageTxtButton(
|
||
|
text: '准备好了',
|
||
|
imgName: 'question/btn_icon_begin',
|
||
|
textStyle: TextStyles.mediumWhite20_014,
|
||
|
width: 186.0,
|
||
|
height: 54.0,
|
||
|
onPressed: () async {
|
||
|
await controller.confirm();
|
||
|
await Get.offAllNamed(Routes.SELECT);
|
||
|
},
|
||
|
),
|
||
|
),
|
||
|
const RSizedBox(
|
||
|
height: 64.0,
|
||
|
),
|
||
|
],
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class _Background extends StatelessWidget {
|
||
|
const _Background();
|
||
|
|
||
|
@override
|
||
|
Widget build(BuildContext context) {
|
||
|
return LayoutBuilder(
|
||
|
builder: (context, constraint) => HookBuilder(builder: (context) {
|
||
|
final controller = SwipeNextPageContainer.of(context)!.controller;
|
||
|
return AnimatedBuilder(
|
||
|
animation: controller,
|
||
|
builder: (context, _) => HookBuilder(builder: (context) {
|
||
|
final hasFinished = useState(false);
|
||
|
final animationValue = hasFinished.value
|
||
|
? controller.upperBound
|
||
|
: controller.value;
|
||
|
useEffect(() {
|
||
|
void onAnimationStateChange(AnimationStatus state) {
|
||
|
if (state == AnimationStatus.completed) {
|
||
|
SchedulerBinding.instance
|
||
|
.addPostFrameCallback((timeStamp) {
|
||
|
hasFinished.value = true;
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
controller.addStatusListener(onAnimationStateChange);
|
||
|
return () {
|
||
|
controller
|
||
|
.removeStatusListener(onAnimationStateChange);
|
||
|
};
|
||
|
}, [controller]);
|
||
|
return Stack(
|
||
|
children: [
|
||
|
Positioned(
|
||
|
top: 0 - animationValue,
|
||
|
left: 0,
|
||
|
right: 0,
|
||
|
child: Container(
|
||
|
width: constraint.maxWidth,
|
||
|
height: constraint.maxHeight,
|
||
|
decoration: const BoxDecoration(
|
||
|
image: DecorationImage(
|
||
|
image: Images.welcomeBg,
|
||
|
fit: BoxFit.fill,
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
Positioned(
|
||
|
top: controller.upperBound - animationValue - 1,
|
||
|
left: 0,
|
||
|
right: 0,
|
||
|
child: Container(
|
||
|
width: constraint.maxWidth,
|
||
|
height: constraint.maxHeight,
|
||
|
decoration: const BoxDecoration(
|
||
|
image: DecorationImage(
|
||
|
image: Images.selectBg,
|
||
|
fit: BoxFit.fill,
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
),
|
||
|
],
|
||
|
);
|
||
|
}));
|
||
|
}));
|
||
|
}
|
||
|
}
|