2020-02-20 10:09:21 +01:00
|
|
|
import 'dart:io';
|
2020-02-09 13:29:09 +01:00
|
|
|
import 'dart:math' as math;
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
2020-02-22 13:25:06 +01:00
|
|
|
import 'package:flutter/services.dart';
|
2020-02-09 13:29:09 +01:00
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'package:flutter_html/flutter_html.dart';
|
2020-02-25 10:57:12 +01:00
|
|
|
import 'package:tsacdop/home/audioplayer.dart';
|
2020-02-09 13:29:09 +01:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2020-02-13 03:51:46 +01:00
|
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
2020-02-21 16:04:02 +01:00
|
|
|
import 'package:intl/intl.dart';
|
2020-02-25 10:57:12 +01:00
|
|
|
import 'package:tuple/tuple.dart';
|
2020-02-11 14:48:11 +01:00
|
|
|
import 'package:tsacdop/class/audiostate.dart';
|
|
|
|
import 'package:tsacdop/class/episodebrief.dart';
|
2020-02-20 10:09:21 +01:00
|
|
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
2020-02-09 13:29:09 +01:00
|
|
|
import 'episodedownload.dart';
|
|
|
|
|
|
|
|
class EpisodeDetail extends StatefulWidget {
|
|
|
|
final EpisodeBrief episodeItem;
|
|
|
|
final String heroTag;
|
2020-02-22 13:25:06 +01:00
|
|
|
EpisodeDetail({this.episodeItem, this.heroTag, Key key}) : super(key: key);
|
2020-02-09 13:29:09 +01:00
|
|
|
|
|
|
|
@override
|
|
|
|
_EpisodeDetailState createState() => _EpisodeDetailState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _EpisodeDetailState extends State<EpisodeDetail> {
|
|
|
|
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
|
|
|
double downloadProgress;
|
|
|
|
bool _loaddes;
|
2020-02-20 10:09:21 +01:00
|
|
|
String path;
|
2020-02-20 16:44:42 +01:00
|
|
|
Future getSDescription(String url) async {
|
2020-02-09 13:29:09 +01:00
|
|
|
var dbHelper = DBHelper();
|
2020-02-20 16:44:42 +01:00
|
|
|
widget.episodeItem.description = await dbHelper.getDescription(url);
|
2020-02-09 13:29:09 +01:00
|
|
|
if (mounted)
|
|
|
|
setState(() {
|
|
|
|
_loaddes = true;
|
|
|
|
});
|
|
|
|
}
|
2020-02-11 14:01:57 +01:00
|
|
|
|
2020-02-09 13:29:09 +01:00
|
|
|
_launchUrl(String url) async {
|
2020-02-11 14:01:57 +01:00
|
|
|
if (await canLaunch(url)) {
|
|
|
|
await launch(url);
|
|
|
|
} else {
|
|
|
|
throw 'Could not launch $url';
|
|
|
|
}
|
2020-02-09 13:29:09 +01:00
|
|
|
}
|
2020-02-11 14:01:57 +01:00
|
|
|
|
2020-02-09 13:29:09 +01:00
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_loaddes = false;
|
2020-02-20 16:44:42 +01:00
|
|
|
getSDescription(widget.episodeItem.enclosureUrl);
|
2020-02-09 13:29:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2020-02-22 13:25:06 +01:00
|
|
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
|
|
|
value: SystemUiOverlayStyle(
|
|
|
|
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
|
|
|
systemNavigationBarColor: Theme.of(context).primaryColor,
|
|
|
|
statusBarColor: Theme.of(context).primaryColor,
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
child: SafeArea(
|
|
|
|
child: Scaffold(
|
|
|
|
backgroundColor: Theme.of(context).primaryColor,
|
|
|
|
appBar: AppBar(
|
|
|
|
title: Text(widget.episodeItem.feedTitle),
|
|
|
|
centerTitle: true,
|
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
body: Stack(
|
|
|
|
children: <Widget>[
|
|
|
|
Container(
|
|
|
|
color: Theme.of(context).primaryColor,
|
|
|
|
padding: EdgeInsets.all(10.0),
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
|
|
|
Container(
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: <Widget>[
|
|
|
|
Container(
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
|
|
|
alignment: Alignment.topLeft,
|
|
|
|
child: Text(
|
|
|
|
widget.episodeItem.title,
|
|
|
|
style: Theme.of(context).textTheme.headline5,
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
|
|
|
Container(
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
|
|
|
height: 30.0,
|
|
|
|
child: Text(
|
|
|
|
'Published ' +
|
|
|
|
DateFormat.yMMMd().format(
|
|
|
|
DateTime.fromMillisecondsSinceEpoch(
|
|
|
|
widget.episodeItem.pubDate)),
|
|
|
|
style: TextStyle(color: Colors.blue[500])),
|
|
|
|
),
|
|
|
|
Container(
|
|
|
|
padding: EdgeInsets.all(12.0),
|
|
|
|
height: 50.0,
|
|
|
|
child: Row(
|
|
|
|
children: <Widget>[
|
|
|
|
(widget.episodeItem.explicit == 1)
|
|
|
|
? Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.red[800],
|
|
|
|
shape: BoxShape.circle),
|
|
|
|
height: 25.0,
|
|
|
|
width: 25.0,
|
|
|
|
margin: EdgeInsets.only(right: 10.0),
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Text('E',
|
|
|
|
style:
|
|
|
|
TextStyle(color: Colors.white)))
|
|
|
|
: Center(),
|
|
|
|
Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.cyan[300],
|
|
|
|
borderRadius: BorderRadius.all(
|
|
|
|
Radius.circular(15.0))),
|
|
|
|
height: 30.0,
|
|
|
|
margin: EdgeInsets.only(right: 10.0),
|
|
|
|
padding:
|
|
|
|
EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Text(
|
|
|
|
(widget.episodeItem.duration).toString() +
|
|
|
|
'mins',
|
|
|
|
style: textstyle),
|
|
|
|
),
|
|
|
|
Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.lightBlue[300],
|
|
|
|
borderRadius: BorderRadius.all(
|
|
|
|
Radius.circular(15.0))),
|
|
|
|
height: 30.0,
|
|
|
|
margin: EdgeInsets.only(right: 10.0),
|
|
|
|
padding:
|
|
|
|
EdgeInsets.symmetric(horizontal: 10.0),
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Text(
|
|
|
|
((widget.episodeItem.enclosureLength) ~/
|
|
|
|
1000000)
|
|
|
|
.toString() +
|
|
|
|
'MB',
|
|
|
|
style: textstyle),
|
|
|
|
),
|
|
|
|
],
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Expanded(
|
|
|
|
child: Container(
|
|
|
|
padding:
|
|
|
|
EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
child: _loaddes
|
|
|
|
? (widget.episodeItem.description.contains('<'))
|
|
|
|
? Html(
|
|
|
|
data: widget.episodeItem.description,
|
|
|
|
onLinkTap: (url) {
|
|
|
|
_launchUrl(url);
|
|
|
|
},
|
|
|
|
useRichText: true,
|
|
|
|
)
|
|
|
|
: Container(
|
|
|
|
alignment: Alignment.topLeft,
|
|
|
|
child:
|
|
|
|
Text(widget.episodeItem.description))
|
|
|
|
: Center(),
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
],
|
2020-02-20 16:44:42 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
|
|
|
Selector<AudioPlayer, bool>(
|
|
|
|
selector: (_, audio) => audio.playerRunning,
|
|
|
|
builder: (_, data, __) {
|
|
|
|
return Container(
|
|
|
|
alignment: Alignment.bottomCenter,
|
|
|
|
padding: EdgeInsets.only(
|
|
|
|
left: 5.0,
|
|
|
|
right: 5.0,
|
|
|
|
bottom: data == true ? 80.0 : 10.0),
|
|
|
|
child: MenuBar(
|
|
|
|
episodeItem: widget.episodeItem,
|
|
|
|
heroTag: widget.heroTag,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
Container(child: PlayerWidget()),
|
|
|
|
],
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class MenuBar extends StatefulWidget {
|
|
|
|
final EpisodeBrief episodeItem;
|
|
|
|
final String heroTag;
|
2020-02-22 13:25:06 +01:00
|
|
|
MenuBar({this.episodeItem, this.heroTag, Key key}) : super(key: key);
|
2020-02-09 13:29:09 +01:00
|
|
|
@override
|
|
|
|
_MenuBarState createState() => _MenuBarState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MenuBarState extends State<MenuBar> {
|
|
|
|
bool _liked;
|
|
|
|
int _like;
|
|
|
|
|
2020-02-20 16:44:42 +01:00
|
|
|
Future<int> saveLiked(String url) async {
|
2020-02-09 13:29:09 +01:00
|
|
|
var dbHelper = DBHelper();
|
2020-02-20 16:44:42 +01:00
|
|
|
int result = await dbHelper.setLiked(url);
|
2020-02-09 13:29:09 +01:00
|
|
|
if (result == 1 && mounted) setState(() => _liked = true);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2020-02-20 16:44:42 +01:00
|
|
|
Future<int> setUnliked(String url) async {
|
2020-02-09 13:29:09 +01:00
|
|
|
var dbHelper = DBHelper();
|
2020-02-20 16:44:42 +01:00
|
|
|
int result = await dbHelper.setUniked(url);
|
2020-02-09 13:29:09 +01:00
|
|
|
if (result == 1 && mounted)
|
|
|
|
setState(() {
|
|
|
|
_liked = false;
|
|
|
|
_like = 0;
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_liked = false;
|
|
|
|
_like = widget.episodeItem.liked;
|
|
|
|
}
|
|
|
|
|
2020-02-20 10:09:21 +01:00
|
|
|
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
|
2020-02-13 03:51:46 +01:00
|
|
|
color: Colors.transparent,
|
|
|
|
child: InkWell(
|
|
|
|
onTap: onTap,
|
|
|
|
child: Container(
|
|
|
|
height: 50.0,
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 15.0),
|
|
|
|
child: widget),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2020-02-09 13:29:09 +01:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2020-02-25 10:57:12 +01:00
|
|
|
var audio = Provider.of<AudioPlayer>(context, listen: false);
|
2020-02-22 13:25:06 +01:00
|
|
|
return Container(
|
|
|
|
height: 50.0,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Theme.of(context).scaffoldBackgroundColor,
|
2020-02-25 10:57:12 +01:00
|
|
|
border: Border.all(
|
|
|
|
color: Theme.of(context).brightness == Brightness.light
|
|
|
|
? Colors.grey[200]
|
|
|
|
: Theme.of(context).scaffoldBackgroundColor,
|
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: <Widget>[
|
2020-02-25 10:57:12 +01:00
|
|
|
Hero(
|
|
|
|
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
|
|
|
|
child: Container(
|
2020-02-25 13:28:48 +01:00
|
|
|
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
2020-02-25 10:57:12 +01:00
|
|
|
child: ClipRRect(
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
|
|
|
child: Container(
|
|
|
|
height: 30.0,
|
|
|
|
width: 30.0,
|
|
|
|
color: Theme.of(context).scaffoldBackgroundColor,
|
|
|
|
child: Image.file(File("${widget.episodeItem.imagePath}")),
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
(_like == 0 && !_liked)
|
|
|
|
? _buttonOnMenu(
|
|
|
|
Icon(
|
|
|
|
Icons.favorite_border,
|
|
|
|
color: Colors.grey[700],
|
2020-02-15 07:39:42 +01:00
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
() => saveLiked(widget.episodeItem.enclosureUrl))
|
2020-02-25 10:57:12 +01:00
|
|
|
: (_like == 1 && !_liked)
|
|
|
|
? _buttonOnMenu(
|
|
|
|
Icon(
|
|
|
|
Icons.favorite,
|
|
|
|
color: Colors.red,
|
|
|
|
),
|
|
|
|
() => setUnliked(widget.episodeItem.enclosureUrl))
|
|
|
|
: Stack(
|
|
|
|
alignment: Alignment.center,
|
|
|
|
children: <Widget>[
|
|
|
|
LoveOpen(),
|
|
|
|
_buttonOnMenu(
|
|
|
|
Icon(
|
|
|
|
Icons.favorite,
|
|
|
|
color: Colors.red,
|
|
|
|
),
|
|
|
|
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
|
|
|
],
|
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
DownloadButton(episodeBrief: widget.episodeItem),
|
2020-02-25 13:28:48 +01:00
|
|
|
Selector<AudioPlayer, List<String>>(
|
|
|
|
selector: (_, audio) => audio.queue.playlist.map((e)=>e.enclosureUrl).toList(),
|
|
|
|
builder: (_, data, __) {
|
|
|
|
print(data.length);
|
|
|
|
return data.contains(widget.episodeItem.enclosureUrl)
|
|
|
|
? _buttonOnMenu(
|
|
|
|
Icon(Icons.playlist_add_check, color: Theme.of(context).accentColor),
|
|
|
|
(){})
|
|
|
|
: _buttonOnMenu(
|
|
|
|
Icon(Icons.playlist_add, color: Colors.grey[700]), () {
|
|
|
|
Fluttertoast.showToast(
|
|
|
|
msg: 'Added to playlist',
|
|
|
|
gravity: ToastGravity.BOTTOM,
|
|
|
|
);
|
|
|
|
audio.addToPlaylist(widget.episodeItem);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
Spacer(),
|
2020-02-25 10:57:12 +01:00
|
|
|
// Text(audio.audioState.toString()),
|
|
|
|
Selector<AudioPlayer, Tuple2<EpisodeBrief, bool>>(
|
|
|
|
selector: (_, audio) =>
|
|
|
|
Tuple2(audio.episode, audio.backgroundAudioPlaying),
|
|
|
|
builder: (_, data, __) {
|
|
|
|
return (widget.episodeItem.title != data.item1?.title)
|
|
|
|
? Material(
|
|
|
|
color: Colors.transparent,
|
|
|
|
child: InkWell(
|
|
|
|
borderRadius: BorderRadius.only(
|
|
|
|
topRight: Radius.circular(5.0),
|
|
|
|
bottomRight: Radius.circular(5.0)),
|
|
|
|
onTap: () {
|
|
|
|
audio.episodeLoad(widget.episodeItem);
|
|
|
|
},
|
|
|
|
child: Container(
|
|
|
|
alignment: Alignment.center,
|
|
|
|
height: 50.0,
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
|
|
|
child: Row(
|
|
|
|
children: <Widget>[
|
|
|
|
Text('Play Now',
|
|
|
|
style: TextStyle(
|
|
|
|
color: Theme.of(context).accentColor,
|
|
|
|
fontSize: 15,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
)),
|
|
|
|
Icon(
|
|
|
|
Icons.play_arrow,
|
2020-02-22 13:25:06 +01:00
|
|
|
color: Theme.of(context).accentColor,
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
|
|
|
],
|
2020-02-22 13:25:06 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
),
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
2020-02-25 10:57:12 +01:00
|
|
|
)
|
|
|
|
: (widget.episodeItem.title == data.item1?.title &&
|
|
|
|
data.item2 == true)
|
|
|
|
? Container(
|
|
|
|
padding: EdgeInsets.only(right: 30),
|
|
|
|
child: SizedBox(
|
|
|
|
width: 20, height: 15, child: WaveLoader()))
|
|
|
|
: Container(
|
|
|
|
padding: EdgeInsets.only(right: 30),
|
|
|
|
child: SizedBox(
|
|
|
|
width: 20,
|
|
|
|
height: 15,
|
|
|
|
child: LineLoader(),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
2020-02-22 13:25:06 +01:00
|
|
|
],
|
|
|
|
),
|
2020-02-09 13:29:09 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LinePainter extends CustomPainter {
|
|
|
|
double _fraction;
|
|
|
|
Paint _paint;
|
|
|
|
LinePainter(this._fraction) {
|
|
|
|
_paint = Paint()
|
|
|
|
..color = Colors.blue
|
|
|
|
..strokeWidth = 2.0
|
|
|
|
..strokeCap = StrokeCap.round;
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void paint(Canvas canvas, Size size) {
|
|
|
|
canvas.drawLine(Offset(0, size.height / 2.0),
|
|
|
|
Offset(size.width * _fraction, size.height / 2.0), _paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool shouldRepaint(LinePainter oldDelegate) {
|
|
|
|
return oldDelegate._fraction != _fraction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LineLoader extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_LineLoaderState createState() => _LineLoaderState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _LineLoaderState extends State<LineLoader>
|
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
|
double _fraction = 0.0;
|
|
|
|
Animation animation;
|
|
|
|
AnimationController controller;
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2020-02-15 07:39:42 +01:00
|
|
|
controller =
|
|
|
|
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
|
2020-02-09 13:29:09 +01:00
|
|
|
animation = Tween(begin: 0.0, end: 1.0).animate(controller)
|
|
|
|
..addListener(() {
|
|
|
|
if (mounted)
|
|
|
|
setState(() {
|
|
|
|
_fraction = animation.value;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
controller.forward();
|
|
|
|
controller.addStatusListener((status) {
|
|
|
|
if (status == AnimationStatus.completed) {
|
|
|
|
controller.reset();
|
|
|
|
} else if (status == AnimationStatus.dismissed) {
|
|
|
|
controller.forward();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
controller.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return CustomPaint(painter: LinePainter(_fraction));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class WavePainter extends CustomPainter {
|
|
|
|
double _fraction;
|
|
|
|
double _value;
|
|
|
|
WavePainter(this._fraction);
|
|
|
|
@override
|
|
|
|
void paint(Canvas canvas, Size size) {
|
|
|
|
if (_fraction < 0.5) {
|
|
|
|
_value = _fraction;
|
|
|
|
} else {
|
|
|
|
_value = 1 - _fraction;
|
|
|
|
}
|
|
|
|
Path _path = Path();
|
|
|
|
Paint _paint = Paint()
|
|
|
|
..color = Colors.blue
|
|
|
|
..strokeWidth = 2.0
|
|
|
|
..strokeCap = StrokeCap.round
|
|
|
|
..style = PaintingStyle.stroke;
|
|
|
|
_path.moveTo(0, size.height / 2);
|
|
|
|
_path.lineTo(0, size.height / 2 + size.height * _value * 0.2);
|
|
|
|
_path.moveTo(0, size.height / 2);
|
|
|
|
_path.lineTo(0, size.height / 2 - size.height * _value * 0.2);
|
|
|
|
_path.moveTo(size.width / 4, size.height / 2);
|
|
|
|
_path.lineTo(size.width / 4, size.height / 2 + size.height * _value * 0.8);
|
|
|
|
_path.moveTo(size.width / 4, size.height / 2);
|
|
|
|
_path.lineTo(size.width / 4, size.height / 2 - size.height * _value * 0.8);
|
|
|
|
_path.moveTo(size.width / 2, size.height / 2);
|
|
|
|
_path.lineTo(size.width / 2, size.height / 2 + size.height * _value * 0.5);
|
|
|
|
_path.moveTo(size.width / 2, size.height / 2);
|
|
|
|
_path.lineTo(size.width / 2, size.height / 2 - size.height * _value * 0.5);
|
|
|
|
_path.moveTo(size.width * 3 / 4, size.height / 2);
|
|
|
|
_path.lineTo(
|
|
|
|
size.width * 3 / 4, size.height / 2 + size.height * _value * 0.6);
|
|
|
|
_path.moveTo(size.width * 3 / 4, size.height / 2);
|
|
|
|
_path.lineTo(
|
|
|
|
size.width * 3 / 4, size.height / 2 - size.height * _value * 0.6);
|
|
|
|
_path.moveTo(size.width, size.height / 2);
|
|
|
|
_path.lineTo(size.width, size.height / 2 + size.height * _value * 0.2);
|
|
|
|
_path.moveTo(size.width, size.height / 2);
|
|
|
|
_path.lineTo(size.width, size.height / 2 - size.height * _value * 0.2);
|
|
|
|
canvas.drawPath(_path, _paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool shouldRepaint(WavePainter oldDelegate) {
|
|
|
|
return oldDelegate._fraction != _fraction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class WaveLoader extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_WaveLoaderState createState() => _WaveLoaderState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _WaveLoaderState extends State<WaveLoader>
|
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
|
double _fraction = 0.0;
|
|
|
|
Animation animation;
|
|
|
|
AnimationController _controller;
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_controller = AnimationController(
|
|
|
|
vsync: this, duration: Duration(milliseconds: 1000));
|
|
|
|
animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
|
|
|
..addListener(() {
|
|
|
|
if (mounted)
|
|
|
|
setState(() {
|
|
|
|
_fraction = animation.value;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
_controller.forward();
|
|
|
|
_controller.addStatusListener((status) {
|
|
|
|
if (status == AnimationStatus.completed) {
|
|
|
|
_controller.reset();
|
|
|
|
} else if (status == AnimationStatus.dismissed) {
|
|
|
|
_controller.forward();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_controller.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return CustomPaint(painter: WavePainter(_fraction));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ImageRotate extends StatefulWidget {
|
2020-02-20 10:09:21 +01:00
|
|
|
final String title;
|
|
|
|
final String path;
|
|
|
|
ImageRotate({this.title, this.path, Key key}) : super(key: key);
|
2020-02-09 13:29:09 +01:00
|
|
|
@override
|
|
|
|
_ImageRotateState createState() => _ImageRotateState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ImageRotateState extends State<ImageRotate>
|
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
|
Animation _animation;
|
|
|
|
AnimationController _controller;
|
|
|
|
double _value;
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
2020-02-14 07:27:40 +01:00
|
|
|
_value = 0;
|
2020-02-09 13:29:09 +01:00
|
|
|
_controller = AnimationController(
|
|
|
|
vsync: this,
|
|
|
|
duration: Duration(milliseconds: 2000),
|
|
|
|
);
|
|
|
|
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
|
|
|
..addListener(() {
|
|
|
|
if (mounted)
|
|
|
|
setState(() {
|
|
|
|
_value = _animation.value;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
_controller.forward();
|
|
|
|
_controller.addStatusListener((status) {
|
|
|
|
if (status == AnimationStatus.completed) {
|
|
|
|
_controller.reset();
|
|
|
|
} else if (status == AnimationStatus.dismissed) {
|
|
|
|
_controller.forward();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_controller.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Transform.rotate(
|
|
|
|
angle: 2 * math.pi * _value,
|
|
|
|
child: Container(
|
|
|
|
padding: EdgeInsets.all(10.0),
|
|
|
|
child: ClipRRect(
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
|
|
|
child: Container(
|
|
|
|
height: 30.0,
|
|
|
|
width: 30.0,
|
|
|
|
color: Colors.white,
|
2020-02-20 16:44:42 +01:00
|
|
|
child: Image.file(File("${widget.path}")),
|
2020-02-09 13:29:09 +01:00
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2020-02-12 17:10:03 +01:00
|
|
|
}
|
2020-02-15 07:39:42 +01:00
|
|
|
|
|
|
|
class LovePainter extends CustomPainter {
|
|
|
|
@override
|
|
|
|
void paint(Canvas canvas, Size size) {
|
|
|
|
Path _path = Path();
|
|
|
|
Paint _paint = Paint()
|
|
|
|
..color = Colors.red
|
|
|
|
..strokeWidth = 2.0
|
|
|
|
..strokeCap = StrokeCap.round;
|
|
|
|
|
|
|
|
_path.moveTo(size.width / 2, size.height / 6);
|
|
|
|
_path.quadraticBezierTo(size.width / 4, 0, size.width / 8, size.height / 6);
|
|
|
|
_path.quadraticBezierTo(
|
|
|
|
0, size.height / 3, size.width / 8, size.height * 0.55);
|
|
|
|
_path.quadraticBezierTo(
|
|
|
|
size.width / 4, size.height * 0.8, size.width / 2, size.height);
|
|
|
|
_path.quadraticBezierTo(size.width * 0.75, size.height * 0.8,
|
|
|
|
size.width * 7 / 8, size.height * 0.55);
|
|
|
|
_path.quadraticBezierTo(
|
|
|
|
size.width, size.height / 3, size.width * 7 / 8, size.height / 6);
|
|
|
|
_path.quadraticBezierTo(
|
|
|
|
size.width * 3 / 4, 0, size.width / 2, size.height / 6);
|
|
|
|
|
|
|
|
canvas.drawPath(_path, _paint);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
bool shouldRepaint(CustomPainter oldDelegate) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LoveOpen extends StatefulWidget {
|
|
|
|
@override
|
|
|
|
_LoveOpenState createState() => _LoveOpenState();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _LoveOpenState extends State<LoveOpen>
|
|
|
|
with SingleTickerProviderStateMixin {
|
|
|
|
Animation _animationA;
|
|
|
|
AnimationController _controller;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_controller = AnimationController(
|
|
|
|
vsync: this,
|
2020-02-20 16:44:42 +01:00
|
|
|
duration: Duration(milliseconds: 300),
|
2020-02-15 07:39:42 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
_animationA = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
|
|
|
..addListener(() {
|
|
|
|
if (mounted) setState(() {});
|
|
|
|
});
|
|
|
|
|
|
|
|
_controller.forward();
|
|
|
|
_controller.addStatusListener((status) {
|
|
|
|
if (status == AnimationStatus.completed) {
|
|
|
|
_controller.reset();
|
2020-02-20 10:09:21 +01:00
|
|
|
}
|
2020-02-15 07:39:42 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_controller.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _littleHeart(double scale, double value, double angle) => Container(
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
padding: EdgeInsets.only(left: value),
|
|
|
|
child: ScaleTransition(
|
|
|
|
scale: _animationA,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Transform.rotate(
|
2020-02-20 10:09:21 +01:00
|
|
|
angle: angle,
|
|
|
|
child: SizedBox(
|
2020-02-15 07:39:42 +01:00
|
|
|
height: 5 * scale,
|
|
|
|
width: 6 * scale,
|
|
|
|
child: CustomPaint(
|
|
|
|
painter: LovePainter(),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Container(
|
|
|
|
width: 50,
|
|
|
|
height: 50,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
|
|
children: <Widget>[
|
|
|
|
Row(
|
|
|
|
children: <Widget>[
|
2020-02-20 10:09:21 +01:00
|
|
|
_littleHeart(0.5, 10, -math.pi / 6),
|
|
|
|
_littleHeart(1.2, 3, 0),
|
2020-02-15 07:39:42 +01:00
|
|
|
],
|
|
|
|
),
|
|
|
|
Row(
|
|
|
|
children: <Widget>[
|
2020-02-20 10:09:21 +01:00
|
|
|
_littleHeart(0.8, 6, math.pi * 1.5),
|
|
|
|
_littleHeart(0.9, 24, math.pi / 2),
|
2020-02-15 07:39:42 +01:00
|
|
|
],
|
|
|
|
),
|
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
|
|
children: <Widget>[
|
2020-02-20 10:09:21 +01:00
|
|
|
_littleHeart(1, 8, -math.pi * 0.7),
|
|
|
|
_littleHeart(0.8, 8, math.pi),
|
|
|
|
_littleHeart(0.6, 3, -math.pi * 1.2)
|
2020-02-15 07:39:42 +01:00
|
|
|
],
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|