diff --git a/lib/home/home.dart b/lib/home/home.dart index d4bc94e..ad0f287 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -521,7 +521,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ), ], ), - child: PlaylistButton()), + child: _PlaylistButton()), ], ), Container(height: 2, color: context.primaryColor), @@ -536,19 +536,15 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { } } -class PlaylistButton extends StatefulWidget { - PlaylistButton({Key key}) : super(key: key); +class _PlaylistButton extends StatefulWidget { + _PlaylistButton({Key key}) : super(key: key); @override - PlaylistButtonState createState() => PlaylistButtonState(); + __PlaylistButtonState createState() => __PlaylistButtonState(); } -class PlaylistButtonState extends State { +class __PlaylistButtonState extends State<_PlaylistButton> { bool _loadPlay; - static String _stringForSeconds(int seconds) { - if (seconds == null) return null; - return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}'; - } _getPlaylist() async { await Provider.of(context, listen: false) @@ -567,7 +563,7 @@ class PlaylistButtonState extends State { @override Widget build(BuildContext context) { - var audio = Provider.of(context, listen: false); + var audio = context.watch(); final s = context.s; return MyPopupMenuButton( shape: RoundedRectangleBorder( @@ -639,7 +635,7 @@ class PlaylistButtonState extends State { child: Column( children: [ Text( - _stringForSeconds(data.item3 ~/ 1000), + (data.item3 ~/ 1000).toTime, // style: // TextStyle(color: Colors.white) ), @@ -654,7 +650,7 @@ class PlaylistButtonState extends State { ), ), Divider( - height: 2, + height: 1, ), ], ), @@ -677,6 +673,9 @@ class PlaylistButtonState extends State { ), ), ), + PopupMenuDivider( + height: 1, + ), PopupMenuItem( value: 2, child: Container( @@ -692,6 +691,9 @@ class PlaylistButtonState extends State { ), ), ), + PopupMenuDivider( + height: 1, + ), ], onSelected: (value) { if (value == 0) { @@ -757,14 +759,14 @@ class _RecentUpdateState extends State<_RecentUpdate> await Future.delayed(Duration(seconds: 3)); if (mounted) { setState(() { - _top = _top + 33; + _top = _top + 30; _loadMore = false; }); } } /// Episodes loaded first time. - int _top = 99; + int _top = 90; /// Load more episodes when scroll to bottom. bool _loadMore; @@ -822,7 +824,9 @@ class _RecentUpdateState extends State<_RecentUpdate> if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && snapshot.data.length == _top) { - _loadMoreEpisode(); + if (!_loadMore) { + _loadMoreEpisode(); + } } return true; }, @@ -1060,13 +1064,13 @@ class _MyFavoriteState extends State<_MyFavorite> await Future.delayed(Duration(seconds: 3)); if (mounted) { setState(() { - _top = _top + 33; + _top = _top + 30; _loadMore = false; }); } } - int _top = 99; + int _top = 90; bool _loadMore; Layout _layout; int _sortBy; @@ -1110,7 +1114,9 @@ class _MyFavoriteState extends State<_MyFavorite> if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && snapshot.data.length == _top) { - _loadMoreEpisode(); + if (!_loadMore) { + _loadMoreEpisode(); + } } return true; }, diff --git a/lib/home/playlist.dart b/lib/home/playlist.dart index ed6f597..4ac4b1e 100644 --- a/lib/home/playlist.dart +++ b/lib/home/playlist.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -515,6 +513,9 @@ class _HistoryList extends StatefulWidget { class __HistoryListState extends State<_HistoryList> { var dbHelper = DBHelper(); + bool _loadMore = false; + Future _getData; + Future> getPlayRecords(int top) async { List playHistory; playHistory = await dbHelper.getPlayRecords(top); @@ -527,208 +528,237 @@ class __HistoryListState extends State<_HistoryList> { _loadMoreData() async { if (mounted) { setState(() { - _top = _top + 20; + _loadMore = true; + }); + } + await Future.delayed(Duration(milliseconds: 500)); + _top = _top + 20; + if (mounted) { + setState(() { + _getData = getPlayRecords(_top); + _loadMore = false; }); } } - int _top = 20; + int _top; + @override + void initState() { + super.initState(); + _top = 20; + _getData = getPlayRecords(_top); + } @override Widget build(BuildContext context) { final s = context.s; final audio = context.watch(); return FutureBuilder>( - future: getPlayRecords(_top), + future: _getData, builder: (context, snapshot) { return snapshot.hasData ? NotificationListener( onNotification: (scrollInfo) { if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && - snapshot.data.length == _top) _loadMoreData(); + snapshot.data.length == _top) { + if (!_loadMore) { + _loadMoreData(); + } + } return true; }, child: ListView.builder( scrollDirection: Axis.vertical, - itemCount: snapshot.data.length, + itemCount: snapshot.data.length + 1, itemBuilder: (context, index) { - final seekValue = snapshot.data[index].seekValue; - final seconds = snapshot.data[index].seconds; - final date = snapshot - .data[index].playdate.millisecondsSinceEpoch; - final episode = snapshot.data[index].episode; - var c = - (Theme.of(context).brightness == Brightness.light) - ? episode.primaryColor.colorizedark() - : episode.primaryColor.colorizeLight(); - return SizedBox( - height: 90.0, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Center( - child: ListTile( - contentPadding: - EdgeInsets.fromLTRB(24, 8, 20, 8), - onTap: () => audio.episodeLoad(episode), - leading: CircleAvatar( - backgroundColor: c.withOpacity(0.5), - backgroundImage: episode.avatarImage), - title: Padding( - padding: - EdgeInsets.symmetric(vertical: 5.0), - child: Text( - snapshot.data[index].title, - maxLines: 1, - overflow: TextOverflow.ellipsis, + if (index == snapshot.data.length) { + return SizedBox( + height: 2, + child: _loadMore + ? LinearProgressIndicator() + : Center()); + } else { + final seekValue = snapshot.data[index].seekValue; + final seconds = snapshot.data[index].seconds; + final date = snapshot + .data[index].playdate.millisecondsSinceEpoch; + final episode = snapshot.data[index].episode; + final c = episode.backgroudColor(context); + return SizedBox( + height: 90.0, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Expanded( + child: Center( + child: ListTile( + contentPadding: + EdgeInsets.fromLTRB(24, 8, 20, 8), + onTap: () => audio.episodeLoad(episode), + leading: CircleAvatar( + backgroundColor: c.withOpacity(0.5), + backgroundImage: episode.avatarImage), + title: Padding( + padding: + EdgeInsets.symmetric(vertical: 5.0), + child: Text( + snapshot.data[index].title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), - subtitle: Container( - height: 35, - child: Row( - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - if (seekValue < 0.9) - Padding( - padding: - const EdgeInsets.symmetric( - vertical: 5.0), - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () async { - audio.episodeLoad(episode, - startPosition: - (seconds * 1000) - .toInt()); - }, - child: Stack(children: [ - ShaderMask( - shaderCallback: (bounds) { - return LinearGradient( - begin: Alignment - .centerLeft, - colors: [ - Colors.cyan[600] - .withOpacity( - 0.8), - Colors.white70 - ], - stops: [ - seekValue, - seekValue - ], - tileMode: - TileMode.mirror, - ).createShader(bounds); - }, - child: Container( - height: 25, - alignment: - Alignment.center, - padding: EdgeInsets - .symmetric( - horizontal: 20), - decoration: - BoxDecoration( - borderRadius: - BorderRadius.all( - Radius - .circular( - 20.0)), - color: context - .accentColor, - ), - child: Text( - seconds.toTime, - style: TextStyle( - color: - Colors.white), + subtitle: Container( + height: 35, + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + if (seekValue < 0.9) + Padding( + padding: + const EdgeInsets.symmetric( + vertical: 5.0), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () async { + audio.episodeLoad(episode, + startPosition: + (seconds * 1000) + .toInt()); + }, + child: Stack(children: [ + ShaderMask( + shaderCallback: + (bounds) { + return LinearGradient( + begin: Alignment + .centerLeft, + colors: [ + Colors.cyan[600] + .withOpacity( + 0.8), + Colors.white70 + ], + stops: [ + seekValue, + seekValue + ], + tileMode: + TileMode.mirror, + ).createShader( + bounds); + }, + child: Container( + height: 25, + alignment: + Alignment.center, + padding: EdgeInsets + .symmetric( + horizontal: + 20), + decoration: + BoxDecoration( + borderRadius: + BorderRadius.all( + Radius.circular( + 20.0)), + color: context + .accentColor, + ), + child: Text( + seconds.toTime, + style: TextStyle( + color: Colors + .white), + ), ), ), - ), - ]), + ]), + ), ), ), + SizedBox( + child: Selector< + AudioPlayerNotifier, + Tuple2, + bool>>( + selector: (_, audio) => Tuple2( + audio.queue.playlist, + audio.queueUpdate), + builder: (_, data, __) { + return data.item1 + .contains(episode) + ? IconButton( + icon: Icon( + Icons + .playlist_add_check, + color: context + .accentColor), + onPressed: () async { + audio + .delFromPlaylist( + episode); + Fluttertoast + .showToast( + msg: s + .toastRemovePlaylist, + gravity: + ToastGravity + .BOTTOM, + ); + }) + : IconButton( + icon: Icon( + Icons + .playlist_add, + color: Colors + .grey[700]), + onPressed: () async { + audio.addToPlaylist( + episode); + Fluttertoast + .showToast( + msg: s + .toastAddPlaylist, + gravity: + ToastGravity + .BOTTOM, + ); + }); + }, + ), ), - SizedBox( - child: Selector< - AudioPlayerNotifier, - Tuple2, - bool>>( - selector: (_, audio) => Tuple2( - audio.queue.playlist, - audio.queueUpdate), - builder: (_, data, __) { - return data.item1 - .contains(episode) - ? IconButton( - icon: Icon( - Icons - .playlist_add_check, - color: context - .accentColor), - onPressed: () async { - audio.delFromPlaylist( - episode); - Fluttertoast - .showToast( - msg: s - .toastRemovePlaylist, - gravity: - ToastGravity - .BOTTOM, - ); - }) - : IconButton( - icon: Icon( - Icons.playlist_add, - color: Colors - .grey[700]), - onPressed: () async { - audio.addToPlaylist( - episode); - Fluttertoast - .showToast( - msg: s - .toastAddPlaylist, - gravity: - ToastGravity - .BOTTOM, - ); - }); - }, + Spacer(), + Text( + date.toDate(context), + style: TextStyle( + fontSize: 15, + ), ), - ), - Spacer(), - Text( - date.toDate(context), - style: TextStyle( - fontSize: 15, - ), - ), - ], + ], + ), ), ), ), ), - ), - Divider(height: 1) - ], - ), - ); + Divider(height: 1) + ], + ), + ); + } }), ) : Center( child: SizedBox( height: 25, width: 25, - child: CircularProgressIndicator()), + child: CircularProgressIndicator( + strokeWidth: 2, + )), ); }); } diff --git a/lib/local_storage/sqflite_localpodcast.dart b/lib/local_storage/sqflite_localpodcast.dart index f340b31..335de08 100644 --- a/lib/local_storage/sqflite_localpodcast.dart +++ b/lib/local_storage/sqflite_localpodcast.dart @@ -235,30 +235,27 @@ class DBHelper { [_milliseconds, 1, id]); } - Future saveHistory(PlayHistory history) async { + Future saveHistory(PlayHistory history) async { var dbClient = await database; - var _milliseconds = DateTime.now().millisecondsSinceEpoch; + final milliseconds = DateTime.now().millisecondsSinceEpoch; var recent = await getPlayHistory(1); - if (recent.length == 1) { - if (recent.first.url == history.url) { - await dbClient.rawDelete("DELETE FROM PlayHistory WHERE add_date = ?", - [recent.first.playdate.millisecondsSinceEpoch]); - } + if (recent.isNotEmpty && recent.first.url == history.url) { + await dbClient.rawDelete("DELETE FROM PlayHistory WHERE add_date = ?", + [recent.first.playdate.millisecondsSinceEpoch]); } - var result = await dbClient.transaction((txn) async { + await dbClient.transaction((txn) async { return await txn.rawInsert( - """REPLACE INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date, listen_time) + """INSERT INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date, listen_time) VALUES (?, ?, ?, ?, ?, ?) """, [ history.title, history.url, history.seconds, history.seekValue, - _milliseconds, + milliseconds, history.seekValue > 0.95 ? 1 : 0 ]); }); - return result; } Future> getPlayHistory(int top) async { diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 937b89b..d956252 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -267,7 +267,7 @@ class AudioPlayerNotifier extends ChangeNotifier { await lastWorkStorage.saveInt(0); } - playlistLoad() async { + Future playlistLoad() async { await _queue.getPlaylist(); _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; @@ -442,11 +442,13 @@ class AudioPlayerNotifier extends ChangeNotifier { await dbHelper.saveHistory(history); } if (event is Map && event['playerRunning'] == false) { - _playerRunning = false; - notifyListeners(); - final history = PlayHistory(_episode.title, _episode.enclosureUrl, - _lastPostion ~/ 1000, _seekSliderValue); - await dbHelper.saveHistory(history); + if (_playerRunning) { + _playerRunning = false; + notifyListeners(); + final history = PlayHistory(_episode.title, _episode.enclosureUrl, + _lastPostion ~/ 1000, _seekSliderValue); + await dbHelper.saveHistory(history); + } } });