mirror of
https://github.com/stonega/tsacdop
synced 2025-02-09 08:08:46 +01:00
Skip at begin
One click to add new episodes to playlist Improve feedback options Bugs fixed
This commit is contained in:
parent
599fc75647
commit
db54bf0bfa
@ -234,11 +234,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
episodeLoad(EpisodeBrief episode, {int startPosition = 0}) async {
|
||||
final EpisodeBrief episodeNew =
|
||||
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||
//TODO load episode from last position when player running
|
||||
if (_playerRunning) {
|
||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000, seekSliderValue);
|
||||
await dbHelper.saveHistory(history);
|
||||
AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0);
|
||||
//if (startPosition > 0) AudioService.seekTo(startPosition);
|
||||
_queue.playlist
|
||||
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||
_queue.playlist.insert(0, episodeNew);
|
||||
@ -256,13 +258,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
_audioState = BasicPlaybackState.connecting;
|
||||
notifyListeners();
|
||||
//await _queue.savePlaylist();
|
||||
_startAudioService(startPosition);
|
||||
_startAudioService(startPosition, episodeNew.enclosureUrl);
|
||||
if (episodeNew.isNew == 1)
|
||||
dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl);
|
||||
}
|
||||
}
|
||||
|
||||
_startAudioService(int position) async {
|
||||
_startAudioService(int position, String url) async {
|
||||
_stopOnComplete = false;
|
||||
_sleepTimerMode = SleepTimerMode.undefined;
|
||||
if (!AudioService.connected) {
|
||||
@ -292,7 +294,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
.listen((item) async {
|
||||
_episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||
_backgroundAudioDuration = item?.duration ?? 0;
|
||||
if (position > 0 && _backgroundAudioDuration > 0) {
|
||||
if (position > 0 &&
|
||||
_backgroundAudioDuration > 0 &&
|
||||
_episode.enclosureUrl == url) {
|
||||
AudioService.seekTo(position);
|
||||
position = 0;
|
||||
}
|
||||
@ -393,7 +397,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
_audioState = BasicPlaybackState.connecting;
|
||||
_queueUpdate = !_queueUpdate;
|
||||
notifyListeners();
|
||||
_startAudioService(_lastPostion ?? 0);
|
||||
_startAudioService(_lastPostion ?? 0, _queue.playlist.first.enclosureUrl);
|
||||
}
|
||||
|
||||
playNext() async {
|
||||
@ -698,7 +702,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
// }
|
||||
else {
|
||||
_playing = true;
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting &&
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
_audioPlayer.play();
|
||||
}
|
||||
@ -712,7 +716,13 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
|
||||
playFromStart() async {
|
||||
_playing = true;
|
||||
_audioPlayer.play();
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
try {
|
||||
_audioPlayer.play();
|
||||
} catch (e) {
|
||||
_setState(state: BasicPlaybackState.error);
|
||||
}
|
||||
if (mediaItem.extras['skip'] > 0) {
|
||||
_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
|
||||
}
|
||||
@ -729,7 +739,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
|
||||
@override
|
||||
void onSeekTo(int position) {
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting &&
|
||||
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
|
||||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none)
|
||||
_audioPlayer.seek(Duration(milliseconds: position));
|
||||
}
|
||||
@ -825,6 +835,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
break;
|
||||
case 'setSpeed':
|
||||
await _audioPlayer.setSpeed(argument);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +216,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
data: _description,
|
||||
linkStyle: TextStyle(
|
||||
color: Theme.of(context).accentColor,
|
||||
// decoration: TextDecoration.underline,
|
||||
// decoration: TextDecoration.underline,
|
||||
textBaseline: TextBaseline.ideographic),
|
||||
onLinkTap: (url) {
|
||||
_launchUrl(url);
|
||||
@ -243,8 +243,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
linkStyle: TextStyle(
|
||||
color:
|
||||
Theme.of(context).accentColor,
|
||||
// decoration:
|
||||
// TextDecoration.underline,
|
||||
// decoration:
|
||||
// TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -280,7 +280,8 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
selector: (_, audio) => audio.playerRunning,
|
||||
builder: (_, data, __) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: data ? 60.0 : 0),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: data ? 60.0 : 0),
|
||||
);
|
||||
}),
|
||||
],
|
||||
@ -379,6 +380,22 @@ class _MenuBarState extends State<MenuBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
OverlayEntry _createOverlayEntry() {
|
||||
RenderBox renderBox = context.findRenderObject();
|
||||
var offset = renderBox.localToGlobal(Offset.zero);
|
||||
return OverlayEntry(
|
||||
builder: (constext) => Positioned(
|
||||
left: offset.dx + 50,
|
||||
top: offset.dy - 60,
|
||||
child: Container(
|
||||
width: 70,
|
||||
height: 100,
|
||||
//color: Colors.grey[200],
|
||||
child: HeartOpen(width: 50, height: 80)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 50.0,
|
||||
decoration: BoxDecoration(
|
||||
@ -418,8 +435,14 @@ class _MenuBarState extends State<MenuBar> {
|
||||
Icon(
|
||||
Icons.favorite_border,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
() => saveLiked(widget.episodeItem.enclosureUrl))
|
||||
), () async {
|
||||
await saveLiked(widget.episodeItem.enclosureUrl);
|
||||
OverlayEntry _overlayEntry;
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
_overlayEntry?.remove();
|
||||
})
|
||||
: (snapshot.data && !_liked)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
@ -427,18 +450,14 @@ class _MenuBarState extends State<MenuBar> {
|
||||
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)),
|
||||
],
|
||||
: _buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
() {
|
||||
setUnliked(widget.episodeItem.enclosureUrl);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
@ -43,6 +44,23 @@ class AboutApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
OverlayEntry _createOverlayEntry(TapDownDetails detail) {
|
||||
// RenderBox renderBox = context.findRenderObject();
|
||||
var offset = detail.globalPosition;
|
||||
return OverlayEntry(
|
||||
builder: (constext) => Positioned(
|
||||
left: offset.dx - 5,
|
||||
top: offset.dy - 120,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 120,
|
||||
color: Colors.transparent,
|
||||
alignment: Alignment.topCenter,
|
||||
child: HeartSet(height: 120, width: 20)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||
@ -108,10 +126,10 @@ class AboutApp extends StatelessWidget {
|
||||
TextStyle(color: Theme.of(context).accentColor),
|
||||
),
|
||||
),
|
||||
_listItem(context, 'GitHub', LineIcons.github,
|
||||
'https://github.com/stonaga/'),
|
||||
_listItem(context, 'Twitter', LineIcons.twitter,
|
||||
'https://twitter.com/shimenmen'),
|
||||
_listItem(context, 'GitHub', LineIcons.github_alt,
|
||||
'https://github.com/stonega'),
|
||||
_listItem(context, 'Medium', LineIcons.medium,
|
||||
'https://medium.com/@stonegate'),
|
||||
],
|
||||
@ -121,28 +139,37 @@ class AboutApp extends StatelessWidget {
|
||||
Container(
|
||||
height: 50,
|
||||
alignment: Alignment.center,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/text.png',
|
||||
height: 25,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
),
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
),
|
||||
FlutterLogo(
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
child: GestureDetector(
|
||||
onTapDown: (detail) async {
|
||||
OverlayEntry _overlayEntry;
|
||||
_overlayEntry = _createOverlayEntry(detail);
|
||||
Overlay.of(context).insert(_overlayEntry);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
_overlayEntry?.remove();
|
||||
},
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Image.asset(
|
||||
'assets/text.png',
|
||||
height: 25,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
),
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.blue,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
),
|
||||
FlutterLogo(
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -470,7 +470,7 @@ class _SearchResultState extends State<SearchResult>
|
||||
setState(() => _issubscribe = true);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Podcast subscribed',
|
||||
gravity: ToastGravity.TOP,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
})
|
||||
: OutlineButton(
|
||||
|
@ -590,7 +590,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||
var doc = parse(description);
|
||||
_description = parse(doc.body.text).documentElement.text;
|
||||
}
|
||||
setState(() => _load = true);
|
||||
if(mounted) setState(() => _load = true);
|
||||
}
|
||||
|
||||
_launchUrl(String url) async {
|
||||
|
@ -49,7 +49,7 @@ class _PodcastManageState extends State<PodcastManage>
|
||||
});
|
||||
});
|
||||
_menuAnimation = Tween(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _menuController, curve: Curves.easeInOutBack))
|
||||
CurvedAnimation(parent: _menuController, curve: Curves.easeIn))
|
||||
..addListener(() {
|
||||
if (mounted) setState(() => _menuValue = _menuAnimation.value);
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -20,7 +21,13 @@ import 'syncing.dart';
|
||||
import 'libries.dart';
|
||||
import 'play_setting.dart';
|
||||
|
||||
class Settings extends StatelessWidget {
|
||||
class Settings extends StatefulWidget {
|
||||
@override
|
||||
_SettingsState createState() => _SettingsState();
|
||||
}
|
||||
|
||||
class _SettingsState extends State<Settings>
|
||||
with SingleTickerProviderStateMixin {
|
||||
_launchUrl(String url) async {
|
||||
if (await canLaunch(url)) {
|
||||
await launch(url);
|
||||
@ -43,6 +50,48 @@ class Settings extends StatelessWidget {
|
||||
print(ompl.toString());
|
||||
}
|
||||
|
||||
bool _showFeedback;
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_showFeedback = false;
|
||||
_value = 0;
|
||||
_controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
|
||||
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
setState(() {
|
||||
_value = _animation.value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget _feedbackItem(IconData icon, String name, String url) => Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => _launchUrl(url),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
icon,
|
||||
size: 20 * _value,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 5),
|
||||
),
|
||||
Text(name)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
@ -110,12 +159,12 @@ class Settings extends StatelessWidget {
|
||||
leading: Icon(LineIcons.play_circle),
|
||||
title: Text('Play'),
|
||||
subtitle: Text('Playlist and player'),
|
||||
// trailing: Selector<AudioPlayerNotifier, bool>(
|
||||
// selector: (_, audio) => audio.autoPlay,
|
||||
// builder: (_, data, __) => Switch(
|
||||
// value: data,
|
||||
// onChanged: (boo) => audio.autoPlaySwitch = boo),
|
||||
// ),
|
||||
// trailing: Selector<AudioPlayerNotifier, bool>(
|
||||
// selector: (_, audio) => audio.autoPlay,
|
||||
// builder: (_, data, __) => Switch(
|
||||
// value: data,
|
||||
// onChanged: (boo) => audio.autoPlaySwitch = boo),
|
||||
// ),
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
@ -215,15 +264,51 @@ class Settings extends StatelessWidget {
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => _launchUrl(
|
||||
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop Feedback'),
|
||||
onTap: () {
|
||||
if (_value == 0)
|
||||
_controller.forward();
|
||||
else
|
||||
_controller.reverse();
|
||||
_showFeedback = !_showFeedback;
|
||||
},
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 25.0),
|
||||
leading: Icon(LineIcons.bug_solid),
|
||||
title: Text('Feedback'),
|
||||
subtitle: Text('Bugs and feature requests'),
|
||||
subtitle: Text('Bugs and feature request'),
|
||||
trailing: Transform.rotate(
|
||||
angle: math.pi * _value,
|
||||
child: Icon(Icons.keyboard_arrow_down),
|
||||
),
|
||||
),
|
||||
_showFeedback
|
||||
? Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
_feedbackItem(
|
||||
LineIcons.github,
|
||||
'Submit issue',
|
||||
'https://github.com/stonega/tsacdop/issues'),
|
||||
_feedbackItem(
|
||||
LineIcons.telegram,
|
||||
'Join group',
|
||||
'https://t.me/joinchat/Bk3LkRpTHy40QYC78PK7Qg'),
|
||||
_feedbackItem(
|
||||
LineIcons.envelope_open_text_solid,
|
||||
'Write to me',
|
||||
'mailto:<tsacdop.app@gmail.com>?subject=Tsacdop Feedback'),
|
||||
_feedbackItem(
|
||||
LineIcons.google_play,
|
||||
'Rate on Play',
|
||||
'https://play.google.com/store/apps/details?id=com.stonegate.tsacdop')
|
||||
],
|
||||
)
|
||||
: Center(),
|
||||
Divider(
|
||||
height: 2,
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
@ -238,6 +323,9 @@ class Settings extends StatelessWidget {
|
||||
Divider(height: 2),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -55,7 +55,7 @@ class StorageSetting extends StatelessWidget {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DownloadsManage())),
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 25, bottom: 10),
|
||||
EdgeInsets.only(left: 80.0, right: 25, bottom: 10, top: 10),
|
||||
title: Text('Ask before using cellular data'),
|
||||
subtitle: Text(
|
||||
'Ask to confirm when using cellular data to download episodes.'),
|
||||
@ -112,7 +112,7 @@ class StorageSetting extends StatelessWidget {
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text('Cache'),
|
||||
subtitle: Text('Audio cache'),
|
||||
subtitle: Text('App cache'),
|
||||
),
|
||||
Divider(height: 2),
|
||||
],
|
||||
|
@ -123,7 +123,7 @@ class ThemeSetting extends StatelessWidget {
|
||||
contentPadding:
|
||||
EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
|
||||
// leading: Icon(Icons.colorize),
|
||||
title: Text('Real Dark'),
|
||||
title: Text('Real Dark',),
|
||||
subtitle: Text(
|
||||
'Turn on if you think the night is not dark enough'),
|
||||
trailing: Selector<SettingState, bool>(
|
||||
|
@ -478,7 +478,7 @@ class _LoveOpenState extends State<LoveOpen>
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: 300),
|
||||
duration: Duration(milliseconds: 1000),
|
||||
);
|
||||
|
||||
_animationA = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
@ -553,3 +553,137 @@ class _LoveOpenState extends State<LoveOpen>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Heart rise
|
||||
class HeartSet extends StatefulWidget {
|
||||
final double height;
|
||||
final double width;
|
||||
HeartSet({Key key, this.height, this.width}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HeartSetState createState() => _HeartSetState();
|
||||
}
|
||||
|
||||
class _HeartSetState extends State<HeartSet>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = 0;
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
|
||||
_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: widget.height,
|
||||
width: widget.width,
|
||||
alignment: Alignment(0.5, 1 - _value),
|
||||
child: Icon(Icons.favorite,
|
||||
color: Colors.blue.withOpacity(0.7), size: 20 * _value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HeartOpen extends StatefulWidget {
|
||||
final double height;
|
||||
final double width;
|
||||
HeartOpen({Key key, this.height, this.width}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HeartOpenState createState() => _HeartOpenState();
|
||||
}
|
||||
|
||||
class _HeartOpenState extends State<HeartOpen>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = 0;
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(seconds: 2),
|
||||
);
|
||||
|
||||
_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _position(int i) {
|
||||
double scale = _list[i];
|
||||
double position = _list[i + 1];
|
||||
return Positioned(
|
||||
left: widget.width * position,
|
||||
bottom: widget.height * _value * scale,
|
||||
child: Icon(Icons.favorite,
|
||||
color: _value > 0.5 ? Colors.red.withOpacity(2 - _value*2) : Colors.red, size: 20 * _value * scale),
|
||||
);
|
||||
}
|
||||
|
||||
List<double> _list =
|
||||
List<double>.generate(20, (index) => math.Random().nextDouble());
|
||||
List<int> _index = List<int>.generate(19, (index) => index);
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: widget.height,
|
||||
width: widget.width,
|
||||
alignment: Alignment(0.5, 1 - _value),
|
||||
child: Icon(Icons.favorite,
|
||||
color: Colors.blue.withOpacity(0.7), size: 20 * _value),
|
||||
),
|
||||
..._index.map<Widget>((e) => _position(e)).toList(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -532,14 +532,13 @@ class _DurationPickerDialogState extends State<_DurationPickerDialog> {
|
||||
snapToMins: widget.snapToMins,
|
||||
)));
|
||||
|
||||
final Widget actions = new ButtonTheme.bar(
|
||||
child: new ButtonBar(children: <Widget>[
|
||||
final Widget actions = ButtonBar(children: <Widget>[
|
||||
new FlatButton(
|
||||
child: new Text(localizations.cancelButtonLabel),
|
||||
onPressed: _handleCancel),
|
||||
new FlatButton(
|
||||
child: new Text(localizations.okButtonLabel), onPressed: _handleOk),
|
||||
]));
|
||||
]);
|
||||
|
||||
final Dialog dialog = new Dialog(child: new OrientationBuilder(
|
||||
builder: (BuildContext context, Orientation orientation) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'context_extension.dart';
|
||||
|
||||
/// Signature for a function that creates a [Widget] to be used within an
|
||||
/// [OpenContainer].
|
||||
@ -736,7 +737,8 @@ class _OpenContainerRoute extends ModalRoute<void> {
|
||||
offset: Offset(rect.left, rect.top),
|
||||
child: SizedBox(
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
height: rect.height *
|
||||
(playerRunning ? (1 - 60 / context.width) : 1),
|
||||
child: Material(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
animationDuration: Duration.zero,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'context_extension.dart';
|
||||
|
||||
//Slide Transition
|
||||
class SlideLeftRoute extends PageRouteBuilder {
|
||||
@ -27,6 +28,38 @@ class SlideLeftRoute extends PageRouteBuilder {
|
||||
);
|
||||
}
|
||||
|
||||
class SlideLeftHideRoute extends PageRouteBuilder {
|
||||
final Widget page;
|
||||
SlideLeftHideRoute({this.page})
|
||||
: super(
|
||||
pageBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) =>
|
||||
page,
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) =>
|
||||
SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
width: context.width,
|
||||
height: context.height,
|
||||
child: child),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class SlideUptRoute extends PageRouteBuilder {
|
||||
final Widget page;
|
||||
SlideUptRoute({this.page})
|
||||
|
Loading…
x
Reference in New Issue
Block a user