397 lines
16 KiB
Dart
397 lines
16 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:mobdr/config/constant.dart';
|
|
import 'package:mobdr/library/flutter_overboard/circular_clipper.dart';
|
|
import 'package:mobdr/library/flutter_overboard/overboard_animator.dart';
|
|
import 'package:mobdr/library/flutter_overboard/page_model.dart';
|
|
import 'package:mobdr/ui/reusable/cache_image_network.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
enum SwipeDirection { LEFT_TO_RIGHT, RIGHT_TO_LEFT, SKIP_TO_LAST }
|
|
|
|
class OverBoard extends StatefulWidget {
|
|
final List<PageModel> pages;
|
|
final Offset? center;
|
|
final bool? showBullets;
|
|
final VoidCallback finishCallback;
|
|
final VoidCallback? skipCallback;
|
|
final OverBoardAnimator? animator;
|
|
final String? skipText, nextText, finishText;
|
|
|
|
OverBoard(
|
|
{Key? key,
|
|
required this.pages,
|
|
this.center,
|
|
this.showBullets,
|
|
this.skipText,
|
|
this.nextText,
|
|
this.finishText,
|
|
required this.finishCallback,
|
|
this.animator,
|
|
this.skipCallback})
|
|
: super(key: key);
|
|
|
|
@override
|
|
_OverBoardState createState() => _OverBoardState();
|
|
}
|
|
|
|
class _OverBoardState extends State<OverBoard> with TickerProviderStateMixin {
|
|
late OverBoardAnimator _animator;
|
|
|
|
ScrollController _scrollController = new ScrollController();
|
|
double _bulletPadding = 5.0, _bulletSize = 10.0, _bulletContainerWidth = 0;
|
|
|
|
int _counter = 0, _last = 0;
|
|
int _total = 0;
|
|
double initial = 0, distance = 0;
|
|
Random random = new Random();
|
|
SwipeDirection _swipeDirection = SwipeDirection.RIGHT_TO_LEFT;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
_animator = new OverBoardAnimator(this);
|
|
_total = widget.pages.length;
|
|
_animate();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
child: _getStack(),
|
|
);
|
|
}
|
|
|
|
_getStack() {
|
|
return GestureDetector(
|
|
onPanStart: (DragStartDetails details) {
|
|
initial = details.globalPosition.dx;
|
|
},
|
|
onPanUpdate: (DragUpdateDetails details) {
|
|
distance = details.globalPosition.dx - initial;
|
|
},
|
|
onPanEnd: (DragEndDetails details) {
|
|
initial = 0.0;
|
|
setState(() {
|
|
_last = _counter;
|
|
});
|
|
if (distance > 1 && _counter > 0) {
|
|
setState(() {
|
|
_counter--;
|
|
_swipeDirection = SwipeDirection.LEFT_TO_RIGHT;
|
|
});
|
|
_animate();
|
|
} else if (distance < 0 && _counter < _total - 1) {
|
|
setState(() {
|
|
_counter++;
|
|
_swipeDirection = SwipeDirection.RIGHT_TO_LEFT;
|
|
});
|
|
_animate();
|
|
}
|
|
},
|
|
child: Stack(
|
|
children: <Widget>[
|
|
_getPage(_last),
|
|
AnimatedBuilder(
|
|
animation: _animator.getAnimator(),
|
|
builder: (context, child) {
|
|
return ClipOval(
|
|
clipper: CircularClipper(
|
|
_animator.getAnimator().value, widget.center),
|
|
child: _getPage(_counter));
|
|
},
|
|
child: Container(),
|
|
),
|
|
Positioned(
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
children: <Widget>[
|
|
Opacity(
|
|
child: TextButton(
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
MaterialStateProperty.resolveWith<Color>(
|
|
(Set<MaterialState> states) => PRIMARY_COLOR,
|
|
),
|
|
),
|
|
onPressed: (widget.skipCallback != null
|
|
? widget.skipCallback
|
|
: _skip),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 18),
|
|
child: Text(widget.skipText ?? "SKIP"),
|
|
)),
|
|
opacity: (_counter < _total - 1) ? 1.0 : 0.0,
|
|
),
|
|
Expanded(
|
|
child: Center(child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
_bulletContainerWidth = constraints.maxWidth - 40.0;
|
|
return Container(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: ((widget.showBullets ?? true)
|
|
? SingleChildScrollView(
|
|
physics: NeverScrollableScrollPhysics(),
|
|
scrollDirection: Axis.horizontal,
|
|
controller: _scrollController,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
for (int i = 0; i < _total; i++)
|
|
Padding(
|
|
padding: EdgeInsets.all(_bulletPadding),
|
|
child: AnimatedContainer(
|
|
duration:
|
|
Duration(milliseconds: 150),
|
|
height: _bulletSize,
|
|
width: (i == _counter)
|
|
? _bulletSize * 2
|
|
: _bulletSize,
|
|
decoration: BoxDecoration(
|
|
color: (i == _counter)
|
|
? PRIMARY_COLOR
|
|
: Colors.grey[300],
|
|
borderRadius:
|
|
BorderRadius.circular(10))),
|
|
)
|
|
],
|
|
),
|
|
)
|
|
: Container()),
|
|
);
|
|
},
|
|
)),
|
|
),
|
|
(_counter < _total - 1
|
|
? TextButton(
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
MaterialStateProperty.resolveWith<Color>(
|
|
(Set<MaterialState> states) => PRIMARY_COLOR,
|
|
),
|
|
),
|
|
onPressed: _next,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Text(widget.nextText ?? "NEXT"),
|
|
))
|
|
: TextButton(
|
|
style: ButtonStyle(
|
|
foregroundColor:
|
|
MaterialStateProperty.resolveWith<Color>(
|
|
(Set<MaterialState> states) => PRIMARY_COLOR,
|
|
),
|
|
),
|
|
onPressed: widget.finishCallback,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Text(widget.finishText ?? "FINISH"),
|
|
))),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
_getPage(index) {
|
|
PageModel page = widget.pages[index];
|
|
return Container(
|
|
width: double.infinity,
|
|
height: double.infinity,
|
|
color: page.color,
|
|
child: page.child != null
|
|
? Center(
|
|
child: page.doAnimateChild!
|
|
? AnimatedBoard(
|
|
animator: _animator,
|
|
child: page.child,
|
|
)
|
|
: page.child,
|
|
)
|
|
: (kIsWeb)
|
|
? SingleChildScrollView(
|
|
child: Padding(
|
|
padding: EdgeInsets.only(top: 64),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
page.doAnimateImage!
|
|
? AnimatedBoard(
|
|
animator: _animator,
|
|
child: new Padding(
|
|
padding: new EdgeInsets.only(bottom: 25.0),
|
|
child: (page.imageAssetPath != null)
|
|
? new Image.asset(page.imageAssetPath!,
|
|
width: 300.0, height: 300.0)
|
|
: buildCacheNetworkImage(
|
|
width: 300,
|
|
height: 300,
|
|
url: page.imageFromUrl,
|
|
plColor: Colors.transparent),
|
|
),
|
|
)
|
|
: new Padding(
|
|
padding: new EdgeInsets.only(bottom: 25.0),
|
|
child: (page.imageAssetPath != null)
|
|
? new Image.asset(page.imageAssetPath!,
|
|
width: 300.0, height: 300.0)
|
|
: buildCacheNetworkImage(
|
|
width: 300,
|
|
height: 300,
|
|
url: page.imageFromUrl,
|
|
plColor: Colors.transparent),
|
|
),
|
|
Padding(
|
|
padding: new EdgeInsets.only(
|
|
top: 10.0, bottom: 30.0, left: 30.0, right: 30.0),
|
|
child: new Text(
|
|
page.title!,
|
|
textAlign: TextAlign.center,
|
|
style: new TextStyle(
|
|
color: Colors.black,
|
|
fontSize: 24.0,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: new EdgeInsets.only(
|
|
bottom: 75.0, left: 30.0, right: 30.0),
|
|
child: new Text(
|
|
page.body!,
|
|
textAlign: TextAlign.center,
|
|
style: new TextStyle(
|
|
color: SOFT_GREY,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: <Widget>[
|
|
page.doAnimateImage!
|
|
? AnimatedBoard(
|
|
animator: _animator,
|
|
child: new Padding(
|
|
padding: new EdgeInsets.only(bottom: 25.0),
|
|
child: (page.imageAssetPath != null)
|
|
? new Image.asset(page.imageAssetPath!,
|
|
width: 300.0, height: 300.0)
|
|
: buildCacheNetworkImage(
|
|
width: 300,
|
|
height: 300,
|
|
url: page.imageFromUrl,
|
|
plColor: Colors.transparent),
|
|
),
|
|
)
|
|
: new Padding(
|
|
padding: new EdgeInsets.only(bottom: 25.0),
|
|
child: (page.imageAssetPath != null)
|
|
? new Image.asset(page.imageAssetPath!,
|
|
width: 300.0, height: 300.0)
|
|
: buildCacheNetworkImage(
|
|
width: 300,
|
|
height: 300,
|
|
url: page.imageFromUrl,
|
|
plColor: Colors.transparent),
|
|
),
|
|
Padding(
|
|
padding: new EdgeInsets.only(
|
|
top: 10.0, bottom: 30.0, left: 30.0, right: 30.0),
|
|
child: new Text(
|
|
page.title!,
|
|
textAlign: TextAlign.center,
|
|
style: new TextStyle(
|
|
color: Colors.black,
|
|
fontSize: 24.0,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: new EdgeInsets.only(
|
|
bottom: 75.0, left: 30.0, right: 30.0),
|
|
child: new Text(
|
|
page.body!,
|
|
textAlign: TextAlign.center,
|
|
style: new TextStyle(
|
|
color: SOFT_GREY,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
_next() {
|
|
setState(() {
|
|
_swipeDirection = SwipeDirection.RIGHT_TO_LEFT;
|
|
_last = _counter;
|
|
_counter++;
|
|
});
|
|
_animate();
|
|
}
|
|
|
|
_skip() {
|
|
setState(() {
|
|
_swipeDirection = SwipeDirection.SKIP_TO_LAST;
|
|
_last = _counter;
|
|
_counter = _total - 1;
|
|
});
|
|
_animate();
|
|
}
|
|
|
|
_animate() {
|
|
_animator.getController().forward(from: 0.0);
|
|
|
|
double _bulletDimension = (_bulletPadding * 2) + (_bulletSize);
|
|
double _scroll = _bulletDimension * _counter;
|
|
double _maxScroll = _bulletDimension * _total - 1;
|
|
if (_scroll > _bulletContainerWidth &&
|
|
_swipeDirection == SwipeDirection.RIGHT_TO_LEFT) {
|
|
double _scrollDistance =
|
|
(((_scroll - _bulletContainerWidth) ~/ _bulletDimension) + 1) *
|
|
_bulletDimension;
|
|
_scrollController.animateTo(_scrollDistance,
|
|
curve: Curves.easeIn, duration: Duration(milliseconds: 100));
|
|
} else if (_scroll < (_maxScroll - _bulletContainerWidth) &&
|
|
_swipeDirection == SwipeDirection.LEFT_TO_RIGHT) {
|
|
_scrollController.animateTo(_scroll,
|
|
curve: Curves.easeIn, duration: Duration(milliseconds: 100));
|
|
} else if (_swipeDirection == SwipeDirection.SKIP_TO_LAST) {
|
|
_scrollController.animateTo(_maxScroll,
|
|
curve: Curves.easeIn, duration: Duration(milliseconds: 100));
|
|
}
|
|
}
|
|
}
|
|
|
|
class AnimatedBoard extends StatelessWidget {
|
|
final Widget? child;
|
|
final OverBoardAnimator? animator;
|
|
|
|
const AnimatedBoard({Key? key, this.animator, this.child}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Transform(
|
|
transform: new Matrix4.translationValues(
|
|
0.0, 50.0 * (1.0 - animator!.getAnimator().value), 0.0),
|
|
child: new Padding(
|
|
padding: new EdgeInsets.only(bottom: 25.0),
|
|
child: child,
|
|
),
|
|
);
|
|
}
|
|
}
|