mirror of
https://github.com/stonega/tsacdop
synced 2025-02-09 08:08:46 +01:00
Improve sleep timer
This commit is contained in:
parent
b268728da7
commit
f1e49a2833
@ -2,7 +2,7 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: cirrusci/flutter:v1.15.17
|
||||
- image: cirrusci/flutter:beta
|
||||
|
||||
branches:
|
||||
only: master
|
||||
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
@ -59,12 +60,11 @@ class PlayHistory {
|
||||
}
|
||||
}
|
||||
|
||||
class Playlist extends ChangeNotifier {
|
||||
class Playlist {
|
||||
String name;
|
||||
DBHelper dbHelper = DBHelper();
|
||||
|
||||
List<EpisodeBrief> _playlist;
|
||||
//list of miediaitem
|
||||
|
||||
List<EpisodeBrief> get playlist => _playlist;
|
||||
KeyValueStorage storage = KeyValueStorage('playlist');
|
||||
@ -80,7 +80,6 @@ class Playlist extends ChangeNotifier {
|
||||
if (episode != null) _playlist.add(episode);
|
||||
});
|
||||
}
|
||||
print('Playlist: ' + _playlist.length.toString());
|
||||
}
|
||||
|
||||
savePlaylist() async {
|
||||
@ -108,6 +107,8 @@ class Playlist extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
enum SleepTimerMode { endOfEpisode, timer, undefined }
|
||||
|
||||
class AudioPlayerNotifier extends ChangeNotifier {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
KeyValueStorage storage = KeyValueStorage('audioposition');
|
||||
@ -128,6 +129,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
int _timeLeft = 0;
|
||||
bool _startSleepTimer = false;
|
||||
double _switchValue = 0;
|
||||
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
|
||||
bool _autoPlay = true;
|
||||
DateTime _current;
|
||||
int _currentPosition;
|
||||
@ -145,6 +147,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
EpisodeBrief get episode => _episode;
|
||||
bool get stopOnComplete => _stopOnComplete;
|
||||
bool get startSleepTimer => _startSleepTimer;
|
||||
SleepTimerMode get sleepTimerMode => _sleepTimerMode;
|
||||
bool get autoPlay => _autoPlay;
|
||||
int get timeLeft => _timeLeft;
|
||||
double get switchValue => _switchValue;
|
||||
@ -159,6 +162,11 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
set setSleepTimerMode(SleepTimerMode timer) {
|
||||
_sleepTimerMode = timer;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void addListener(VoidCallback listener) async {
|
||||
super.addListener(listener);
|
||||
@ -173,7 +181,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
_lastPostion = await storage.getInt();
|
||||
if (_lastPostion > 0 && _queue.playlist.length > 0) {
|
||||
final EpisodeBrief episode = _queue.playlist.first;
|
||||
final int duration = episode.enclosureLength * 60;
|
||||
final int duration = episode.duration * 1000;
|
||||
final double seekValue = duration != 0 ? _lastPostion / duration : 1;
|
||||
final PlayHistory history = PlayHistory(
|
||||
episode.title, episode.enclosureUrl, _lastPostion / 1000, seekValue);
|
||||
@ -196,22 +204,25 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
await _queue.savePlaylist();
|
||||
} else {
|
||||
await _queue.getPlaylist();
|
||||
_queue.playlist
|
||||
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||
_queue.playlist.insert(0, episodeNew);
|
||||
_queue.savePlaylist();
|
||||
// _queue.playlist
|
||||
// .removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||
await _queue.delFromPlaylist(episode);
|
||||
await _queue.addToPlayListAt(episodeNew, 0);
|
||||
_backgroundAudioDuration = 0;
|
||||
_backgroundAudioPosition = 0;
|
||||
_seekSliderValue = 0;
|
||||
_episode = episodeNew;
|
||||
_playerRunning = true;
|
||||
_audioState = BasicPlaybackState.connecting;
|
||||
notifyListeners();
|
||||
await _queue.savePlaylist();
|
||||
//await _queue.savePlaylist();
|
||||
_startAudioService(0);
|
||||
}
|
||||
}
|
||||
|
||||
_startAudioService(int position) async {
|
||||
_stopOnComplete = false;
|
||||
_sleepTimerMode = SleepTimerMode.undefined;
|
||||
if (!AudioService.connected) {
|
||||
await AudioService.connect();
|
||||
}
|
||||
@ -223,7 +234,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
enableQueue: true,
|
||||
androidStopOnRemoveTask: true,
|
||||
androidStopForegroundOnPause: true);
|
||||
_playerRunning = true;
|
||||
|
||||
if (_autoPlay) {
|
||||
await Future.forEach(_queue.playlist, (episode) async {
|
||||
await AudioService.addQueueItem(episode.toMediaItem());
|
||||
@ -231,33 +242,64 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
} else {
|
||||
await AudioService.addQueueItem(_queue.playlist.first.toMediaItem());
|
||||
}
|
||||
_playerRunning = true;
|
||||
await AudioService.play();
|
||||
AudioService.currentMediaItemStream.listen((item) async {
|
||||
if (item != null) {
|
||||
_episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||
_backgroundAudioDuration = item?.duration ?? 0;
|
||||
if (position > 0 && _backgroundAudioDuration > 0) {
|
||||
AudioService.seekTo(position);
|
||||
position = 0;
|
||||
}
|
||||
|
||||
AudioService.currentMediaItemStream
|
||||
.where((event) => event != null)
|
||||
.listen((item) async {
|
||||
_episode = await dbHelper.getRssItemWithMediaId(item.id);
|
||||
_backgroundAudioDuration = item?.duration ?? 0;
|
||||
if (position > 0 && _backgroundAudioDuration > 0) {
|
||||
AudioService.seekTo(position);
|
||||
position = 0;
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
var queueSubject = BehaviorSubject<List<MediaItem>>();
|
||||
queueSubject.addStream(
|
||||
AudioService.queueStream.distinct().where((event) => event != null));
|
||||
queueSubject.stream.listen((event) {
|
||||
print(event.length);
|
||||
if (event.length == _queue.playlist.length - 1 &&
|
||||
_audioState == BasicPlaybackState.skippingToNext) {
|
||||
if (event.length == 0 || _stopOnComplete == true) {
|
||||
_queue.delFromPlaylist(_episode);
|
||||
_lastPostion = 0;
|
||||
storage.saveInt(_lastPostion);
|
||||
final PlayHistory history = PlayHistory(
|
||||
_episode.title,
|
||||
_episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000,
|
||||
seekSliderValue);
|
||||
dbHelper.saveHistory(history);
|
||||
} else if (event.first.id != _episode.mediaId) {
|
||||
_queue.delFromPlaylist(_episode);
|
||||
final PlayHistory history = PlayHistory(
|
||||
_episode.title,
|
||||
_episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000,
|
||||
seekSliderValue);
|
||||
dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AudioService.playbackStateStream.listen((event) async {
|
||||
_current = DateTime.now();
|
||||
_audioState = event?.basicState;
|
||||
if (_audioState == BasicPlaybackState.skippingToNext &&
|
||||
_episode != null) {
|
||||
print(_episode.title);
|
||||
_queue.delFromPlaylist(_episode);
|
||||
}
|
||||
if (_audioState == BasicPlaybackState.skippingToNext &&
|
||||
_episode != null &&
|
||||
_backgroundAudioPosition > 0) {
|
||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
_backgroundAudioPosition / 1000, _seekSliderValue);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
// if (_audioState == BasicPlaybackState.skippingToNext &&
|
||||
// _episode != null) {
|
||||
// print(_episode.title);
|
||||
// _queue.delFromPlaylist(_episode);
|
||||
// }
|
||||
// if (_audioState == BasicPlaybackState.skippingToNext &&
|
||||
// _episode != null &&
|
||||
// _backgroundAudioPosition > 0) {
|
||||
// PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||
// _backgroundAudioPosition / 1000, _seekSliderValue);
|
||||
// await dbHelper.saveHistory(history);
|
||||
// }
|
||||
if (_audioState == BasicPlaybackState.stopped) _playerRunning = false;
|
||||
|
||||
if (_audioState == BasicPlaybackState.error) {
|
||||
@ -296,23 +338,23 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
storage.saveInt(_lastPostion);
|
||||
}
|
||||
|
||||
if ((_queue.playlist.length == 1 || !_autoPlay) &&
|
||||
_seekSliderValue > 0.9 &&
|
||||
_episode != null) {
|
||||
_queue.delFromPlaylist(_episode);
|
||||
_lastPostion = 0;
|
||||
storage.saveInt(_lastPostion);
|
||||
final PlayHistory history = PlayHistory(
|
||||
_episode.title,
|
||||
_episode.enclosureUrl,
|
||||
backgroundAudioPosition / 1000,
|
||||
seekSliderValue);
|
||||
dbHelper.saveHistory(history);
|
||||
}
|
||||
// if ((_queue.playlist.length == 1 || !_autoPlay) &&
|
||||
// _seekSli;lderValue > 0.9 &&
|
||||
// _episode != null &&
|
||||
// _audioState != BasicPlaybackState.connecting) {
|
||||
// _queue.delFromPlaylist(_episode);
|
||||
// _lastPostion = 0;
|
||||
// storage.saveInt(_lastPostion);
|
||||
// final PlayHistory history = PlayHistory(
|
||||
// _episode.title,
|
||||
// _episode.enclosureUrl,
|
||||
// backgroundAudioPosition / 1000,
|
||||
// seekSliderValue);
|
||||
// dbHelper.saveHistory(history);
|
||||
// }
|
||||
notifyListeners();
|
||||
}
|
||||
if (_audioState == BasicPlaybackState.stopped ||
|
||||
_playerRunning == false) {
|
||||
if (_audioState == BasicPlaybackState.stopped) {
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
@ -325,6 +367,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
_seekSliderValue = 0;
|
||||
_episode = _queue.playlist.first;
|
||||
_playerRunning = true;
|
||||
_audioState = BasicPlaybackState.connecting;
|
||||
_queueUpdate = !_queueUpdate;
|
||||
notifyListeners();
|
||||
_startAudioService(_lastPostion ?? 0);
|
||||
@ -338,7 +381,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
if (_playerRunning) {
|
||||
await AudioService.addQueueItem(episode.toMediaItem());
|
||||
}
|
||||
print('add to playlist when not rnnning');
|
||||
await _queue.addToPlayList(episode);
|
||||
notifyListeners();
|
||||
}
|
||||
@ -347,7 +389,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
if (_playerRunning) {
|
||||
await AudioService.addQueueItemAt(episode.toMediaItem(), index);
|
||||
}
|
||||
print('add to playlist when not rnnning');
|
||||
await _queue.addToPlayListAt(episode, index);
|
||||
_queueUpdate = !_queueUpdate;
|
||||
notifyListeners();
|
||||
@ -421,43 +462,60 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
||||
|
||||
//Set sleep timer
|
||||
sleepTimer(int mins) {
|
||||
_startSleepTimer = true;
|
||||
_switchValue = 1;
|
||||
notifyListeners();
|
||||
_timeLeft = mins * 60;
|
||||
Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
if (_timeLeft == 0) {
|
||||
timer.cancel();
|
||||
notifyListeners();
|
||||
} else {
|
||||
_timeLeft = _timeLeft - 1;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
_stopTimer = Timer(Duration(minutes: mins), () {
|
||||
_stopOnComplete = false;
|
||||
_startSleepTimer = false;
|
||||
_switchValue = 0;
|
||||
_playerRunning = false;
|
||||
if (_sleepTimerMode == SleepTimerMode.timer) {
|
||||
_startSleepTimer = true;
|
||||
_switchValue = 1;
|
||||
notifyListeners();
|
||||
AudioService.stop();
|
||||
AudioService.disconnect();
|
||||
});
|
||||
_timeLeft = mins * 60;
|
||||
Timer.periodic(Duration(seconds: 1), (timer) {
|
||||
if (_timeLeft == 0) {
|
||||
timer.cancel();
|
||||
notifyListeners();
|
||||
} else {
|
||||
_timeLeft = _timeLeft - 1;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
_stopTimer = Timer(Duration(minutes: mins), () {
|
||||
_stopOnComplete = false;
|
||||
_startSleepTimer = false;
|
||||
_switchValue = 0;
|
||||
_playerRunning = false;
|
||||
notifyListeners();
|
||||
AudioService.stop();
|
||||
AudioService.disconnect();
|
||||
});
|
||||
} else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) {
|
||||
_stopOnComplete = true;
|
||||
_switchValue = 1;
|
||||
notifyListeners();
|
||||
if (_queue.playlist.length > 1 && _autoPlay) {
|
||||
AudioService.customAction('stopAtEnd');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Cancel sleep timer
|
||||
cancelTimer() {
|
||||
_stopTimer.cancel();
|
||||
_timeLeft = 0;
|
||||
_startSleepTimer = false;
|
||||
_switchValue = 0;
|
||||
notifyListeners();
|
||||
if (_sleepTimerMode == SleepTimerMode.timer) {
|
||||
_stopTimer.cancel();
|
||||
_timeLeft = 0;
|
||||
_startSleepTimer = false;
|
||||
_switchValue = 0;
|
||||
notifyListeners();
|
||||
} else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) {
|
||||
AudioService.customAction('cancelStopAtEnd');
|
||||
_switchValue = 0;
|
||||
_stopOnComplete = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
await AudioService.stop();
|
||||
await AudioService.disconnect();
|
||||
//_playerRunning = false;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@ -468,6 +526,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
Completer _completer = Completer();
|
||||
BasicPlaybackState _skipState;
|
||||
bool _playing;
|
||||
bool _stopAtEnd;
|
||||
|
||||
bool get hasNext => _queue.length > 0;
|
||||
|
||||
@ -494,20 +553,19 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
|
||||
@override
|
||||
Future<void> onStart() async {
|
||||
print('start background task');
|
||||
_stopAtEnd = false;
|
||||
var playerStateSubscription = _audioPlayer.playbackStateStream
|
||||
.where((state) => state == AudioPlaybackState.completed)
|
||||
.listen((state) {
|
||||
_handlePlaybackCompleted();
|
||||
});
|
||||
var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
|
||||
print('buffer position' + event.bufferedPosition.toString());
|
||||
if (event.playbackError != null) {
|
||||
_setState(state: BasicPlaybackState.error);
|
||||
}
|
||||
BasicPlaybackState state;
|
||||
if (event.buffering) {
|
||||
state = BasicPlaybackState.buffering;
|
||||
state = _skipState ?? BasicPlaybackState.buffering;
|
||||
} else {
|
||||
state = _stateToBasicState(event.state);
|
||||
}
|
||||
@ -523,14 +581,13 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
eventSubscription.cancel();
|
||||
}
|
||||
|
||||
void _handlePlaybackCompleted() {
|
||||
void _handlePlaybackCompleted() async {
|
||||
if (hasNext) {
|
||||
onSkipToNext();
|
||||
} else {
|
||||
_skipState = BasicPlaybackState.skippingToNext;
|
||||
_audioPlayer.stop();
|
||||
_queue.removeAt(0);
|
||||
_skipState = null;
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
onStop();
|
||||
}
|
||||
}
|
||||
@ -544,32 +601,28 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
|
||||
@override
|
||||
Future<void> onSkipToNext() async {
|
||||
if (_playing == null) {
|
||||
// First time, we want to start playing
|
||||
_playing = true;
|
||||
} else {
|
||||
// Stop current item
|
||||
await _audioPlayer.stop();
|
||||
_queue.removeAt(0);
|
||||
}
|
||||
if (_queue.length == 0) {
|
||||
_skipState = BasicPlaybackState.skippingToNext;
|
||||
await _audioPlayer.stop();
|
||||
_queue.removeAt(0);
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
// }
|
||||
if (_queue.length == 0 || _stopAtEnd) {
|
||||
_skipState = null;
|
||||
onStop();
|
||||
} else {
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
// AudioServiceBackground.setQueue(_queue);
|
||||
AudioServiceBackground.setMediaItem(mediaItem);
|
||||
_skipState = BasicPlaybackState.skippingToNext;
|
||||
await _audioPlayer.setUrl(mediaItem.id);
|
||||
print(mediaItem.id);
|
||||
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||
AudioServiceBackground.setMediaItem(
|
||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
||||
_skipState = null;
|
||||
// Resume playback if we were playing
|
||||
if (_playing) {
|
||||
onPlay();
|
||||
} else {
|
||||
_setState(state: BasicPlaybackState.paused);
|
||||
}
|
||||
// if (_playing) {
|
||||
onPlay();
|
||||
// } else {
|
||||
// _setState(state: BasicPlaybackState.paused);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,7 +631,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
if (_skipState == null) {
|
||||
if (_playing == null) {
|
||||
_playing = true;
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
// await AudioServiceBackground.setQueue(_queue);
|
||||
await _audioPlayer.setUrl(mediaItem.id);
|
||||
Duration duration = await _audioPlayer.durationFuture;
|
||||
AudioServiceBackground.setMediaItem(
|
||||
@ -618,7 +671,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
@override
|
||||
void onAddQueueItem(MediaItem mediaItem) async {
|
||||
_queue.add(mediaItem);
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -633,8 +686,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
await _audioPlayer.stop();
|
||||
_queue.removeWhere((item) => item.id == mediaItem.id);
|
||||
_queue.insert(0, mediaItem);
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setMediaItem(mediaItem);
|
||||
await _audioPlayer.setUrl(mediaItem.id);
|
||||
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||
AudioServiceBackground.setMediaItem(
|
||||
@ -642,7 +695,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
onPlay();
|
||||
} else {
|
||||
_queue.insert(index, mediaItem);
|
||||
AudioServiceBackground.setQueue(_queue);
|
||||
await AudioServiceBackground.setQueue(_queue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -684,9 +737,11 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
||||
@override
|
||||
void onCustomAction(funtion, argument) {
|
||||
switch (funtion) {
|
||||
case 'addQueue':
|
||||
case 'stopAtEnd':
|
||||
_stopAtEnd = true;
|
||||
break;
|
||||
case 'updateMedia':
|
||||
case 'cancelStopAtEnd':
|
||||
_stopAtEnd = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -90,13 +90,16 @@ class DownloadState extends ChangeNotifier {
|
||||
Future _saveMediaId(EpisodeTask episodeTask) async {
|
||||
episodeTask.status = DownloadTaskStatus.complete;
|
||||
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
||||
query:
|
||||
"SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
||||
query: "SELECT * FROM task WHERE task_id = '${episodeTask.taskId}'");
|
||||
String filePath = 'file://' +
|
||||
path.join(completeTask.first.savedDir, completeTask.first.filename);
|
||||
print(filePath);
|
||||
dbHelper.saveMediaId(
|
||||
episodeTask.episode.enclosureUrl, filePath, episodeTask.taskId);
|
||||
EpisodeBrief episode =
|
||||
await dbHelper.getRssItemWithUrl(episodeTask.episode.enclosureUrl);
|
||||
_removeTask(episodeTask.episode);
|
||||
_episodeTasks.add(EpisodeTask(episode, episodeTask.taskId,
|
||||
progress: 100, status: DownloadTaskStatus.complete));
|
||||
}
|
||||
|
||||
void _unbindBackgroundIsolate() {
|
||||
|
@ -125,7 +125,7 @@ class SettingState extends ChangeNotifier {
|
||||
int color = int.parse('FF' + colorString.toUpperCase(), radix: 16);
|
||||
_accentSetColor = Color(color).withOpacity(1.0);
|
||||
} else {
|
||||
_accentSetColor = Color.fromRGBO(35, 204, 198, 1);
|
||||
_accentSetColor = Colors.teal[500];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -17,6 +16,7 @@ import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
import 'episodedownload.dart';
|
||||
|
||||
class EpisodeDetail extends StatefulWidget {
|
||||
@ -163,7 +163,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
(widget.episodeItem.duration)
|
||||
(widget.episodeItem.duration ~/ 60)
|
||||
.toString() +
|
||||
'mins',
|
||||
style: textstyle),
|
||||
@ -253,10 +253,15 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
'assets/shownote.png'),
|
||||
height: 100.0,
|
||||
),
|
||||
Padding(padding: EdgeInsets.all(5.0)),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(5.0)),
|
||||
Text(
|
||||
'Still no shownote received\n for this episode.', textAlign: TextAlign.center,
|
||||
style: TextStyle(color: context.textTheme.bodyText1.color.withOpacity(0.5))),
|
||||
'Still no shownote received\n for this episode.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: context.textTheme
|
||||
.bodyText1.color
|
||||
.withOpacity(0.5))),
|
||||
],
|
||||
),
|
||||
)
|
||||
@ -313,7 +318,11 @@ class MenuBar extends StatefulWidget {
|
||||
|
||||
class _MenuBarState extends State<MenuBar> {
|
||||
bool _liked;
|
||||
int _like;
|
||||
|
||||
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
|
||||
var dbHelper = DBHelper();
|
||||
return await dbHelper.getPosition(episode);
|
||||
}
|
||||
|
||||
Future<int> saveLiked(String url) async {
|
||||
var dbHelper = DBHelper();
|
||||
@ -328,16 +337,25 @@ class _MenuBarState extends State<MenuBar> {
|
||||
if (result == 1 && mounted)
|
||||
setState(() {
|
||||
_liked = false;
|
||||
_like = 0;
|
||||
// _like = 0;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
static String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_liked = false;
|
||||
_like = widget.episodeItem.liked;
|
||||
}
|
||||
|
||||
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
|
||||
@ -384,32 +402,39 @@ class _MenuBarState extends State<MenuBar> {
|
||||
),
|
||||
),
|
||||
),
|
||||
(_like == 0 && !_liked)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite_border,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
() => saveLiked(widget.episodeItem.enclosureUrl))
|
||||
: (_like == 1 && !_liked)
|
||||
FutureBuilder<bool>(
|
||||
future: _isLiked(widget.episodeItem),
|
||||
initialData: false,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
return (!snapshot.data && !_liked)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
Icons.favorite_border,
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
() => setUnliked(widget.episodeItem.enclosureUrl))
|
||||
: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
LoveOpen(),
|
||||
_buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
||||
],
|
||||
),
|
||||
() => saveLiked(widget.episodeItem.enclosureUrl))
|
||||
: (snapshot.data && !_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)),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
DownloadButton(episode: widget.episodeItem),
|
||||
Selector<AudioPlayerNotifier, List<String>>(
|
||||
selector: (_, audio) =>
|
||||
@ -418,8 +443,13 @@ class _MenuBarState extends State<MenuBar> {
|
||||
return data.contains(widget.episodeItem.enclosureUrl)
|
||||
? _buttonOnMenu(
|
||||
Icon(Icons.playlist_add_check,
|
||||
color: Theme.of(context).accentColor),
|
||||
() {})
|
||||
color: Theme.of(context).accentColor), () {
|
||||
audio.delFromPlaylist(widget.episodeItem);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Removed from playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
})
|
||||
: _buttonOnMenu(
|
||||
Icon(Icons.playlist_add, color: Colors.grey[700]), () {
|
||||
Fluttertoast.showToast(
|
||||
@ -430,8 +460,61 @@ class _MenuBarState extends State<MenuBar> {
|
||||
});
|
||||
},
|
||||
),
|
||||
FutureBuilder<PlayHistory>(
|
||||
future: getPosition(widget.episodeItem),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return snapshot.hasData
|
||||
? snapshot.data.seekValue > 0.95
|
||||
? Container(
|
||||
height: 20,
|
||||
padding: EdgeInsets.symmetric(horizontal:15),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CustomPaint(
|
||||
painter: ListenedPainter(context.accentColor,
|
||||
stroke: 2.0),
|
||||
),
|
||||
),
|
||||
)
|
||||
: snapshot.data.seconds < 0.1
|
||||
? Center()
|
||||
: Container(
|
||||
height: 50,
|
||||
padding: EdgeInsets.symmetric(horizontal:15),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CustomPaint(
|
||||
painter: ListenedPainter(
|
||||
context.accentColor,
|
||||
stroke: 2.0),
|
||||
),
|
||||
),
|
||||
Padding(padding: EdgeInsets.symmetric(horizontal:2)),
|
||||
Container(
|
||||
height: 20,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0)),
|
||||
color: context.accentColor,
|
||||
),
|
||||
child: Text(
|
||||
_stringForSeconds(snapshot.data.seconds),
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Center();
|
||||
}),
|
||||
Spacer(),
|
||||
// Text(audio.audioState.toString()),
|
||||
Selector<AudioPlayerNotifier,
|
||||
Tuple2<EpisodeBrief, BasicPlaybackState>>(
|
||||
selector: (_, audio) => Tuple2(audio.episode, audio.audioState),
|
||||
@ -464,20 +547,12 @@ class _MenuBarState extends State<MenuBar> {
|
||||
),
|
||||
),
|
||||
)
|
||||
: (widget.episodeItem.title == data.item1?.title &&
|
||||
data.item2 == BasicPlaybackState.playing)
|
||||
? 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(),
|
||||
),
|
||||
);
|
||||
: Container(
|
||||
padding: EdgeInsets.only(right: 30),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 15,
|
||||
child: WaveLoader(color: context.accentColor)));
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -485,351 +560,3 @@ class _MenuBarState extends State<MenuBar> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LinePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
Paint _paint;
|
||||
Color _maincolor;
|
||||
LinePainter(this._fraction, this._maincolor) {
|
||||
_paint = Paint()
|
||||
..color = _maincolor
|
||||
..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();
|
||||
controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
|
||||
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, Theme.of(context).accentColor));
|
||||
}
|
||||
}
|
||||
|
||||
class WavePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
double _value;
|
||||
Color _color;
|
||||
WavePainter(this._fraction, this._color);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (_fraction < 0.5) {
|
||||
_value = _fraction;
|
||||
} else {
|
||||
_value = 1 - _fraction;
|
||||
}
|
||||
Path _path = Path();
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
..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, Theme.of(context).accentColor));
|
||||
}
|
||||
}
|
||||
|
||||
class ImageRotate extends StatefulWidget {
|
||||
final String title;
|
||||
final String path;
|
||||
ImageRotate({this.title, this.path, Key key}) : super(key: key);
|
||||
@override
|
||||
_ImageRotateState createState() => _ImageRotateState();
|
||||
}
|
||||
|
||||
class _ImageRotateState extends State<ImageRotate>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = 0;
|
||||
_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,
|
||||
child: Image.file(File("${widget.path}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
duration: Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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(
|
||||
angle: angle,
|
||||
child: SizedBox(
|
||||
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>[
|
||||
_littleHeart(0.5, 10, -math.pi / 6),
|
||||
_littleHeart(1.2, 3, 0),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
_littleHeart(0.8, 6, math.pi * 1.5),
|
||||
_littleHeart(0.9, 24, math.pi / 2),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_littleHeart(1, 8, -math.pi * 0.7),
|
||||
_littleHeart(0.8, 8, math.pi),
|
||||
_littleHeart(0.6, 3, -math.pi * 1.2)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
@ -17,7 +16,9 @@ import 'package:tsacdop/episodes/episodedetail.dart';
|
||||
import 'package:tsacdop/home/audiopanel.dart';
|
||||
import 'package:tsacdop/util/pageroute.dart';
|
||||
import 'package:tsacdop/util/colorize.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/util/day_night_switch.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
|
||||
class MyRectangularTrackShape extends RectangularSliderTrackShape {
|
||||
Rect getPreferredRect({
|
||||
@ -85,18 +86,6 @@ class MyRoundSliderThumpShape extends SliderComponentShape {
|
||||
..strokeWidth = 2,
|
||||
);
|
||||
|
||||
// Path _path = Path();
|
||||
|
||||
// _path.moveTo(center.dx - 12, center.dy + 10);
|
||||
// _path.lineTo(center.dx - 12, center.dy - 12);
|
||||
// _path.lineTo(center.dx -12, center.dy - 12);
|
||||
// canvas.drawShadow(_path, Colors.black, 4, false);
|
||||
|
||||
// Path _pathLight = Path();
|
||||
// _pathLight.moveTo(center.dx + 12, center.dy - 12);
|
||||
// _pathLight.lineTo(center.dx + 12, center.dy + 10);
|
||||
//// _pathLight.lineTo(center.dx - 12, center.dy + 10);
|
||||
// canvas.drawShadow(_pathLight, Colors.black, 4, true);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(
|
||||
center.dx - 10, center.dy + 10, center.dx + 10, center.dy - 10),
|
||||
@ -117,33 +106,33 @@ class MyRoundSliderThumpShape extends SliderComponentShape {
|
||||
}
|
||||
}
|
||||
|
||||
final List<BoxShadow> _customShadow = [
|
||||
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
||||
BoxShadow(
|
||||
blurRadius: 8,
|
||||
offset: Offset(2, 2),
|
||||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
|
||||
final List<BoxShadow> _customShadowNight = [
|
||||
BoxShadow(
|
||||
blurRadius: 6,
|
||||
offset: Offset(-1, -1),
|
||||
color: Colors.grey[100].withOpacity(0.3)),
|
||||
BoxShadow(blurRadius: 8, offset: Offset(2, 2), color: Colors.black)
|
||||
];
|
||||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
class PlayerWidget extends StatefulWidget {
|
||||
@override
|
||||
_PlayerWidgetState createState() => _PlayerWidgetState();
|
||||
}
|
||||
|
||||
class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
static String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
List<BoxShadow> _customShadow = [
|
||||
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
||||
BoxShadow(
|
||||
blurRadius: 8,
|
||||
offset: Offset(2, 2),
|
||||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
|
||||
List<BoxShadow> _customShadowNight = [
|
||||
BoxShadow(
|
||||
blurRadius: 6,
|
||||
offset: Offset(-1, -1),
|
||||
color: Colors.grey[100].withOpacity(0.3)),
|
||||
BoxShadow(blurRadius: 8, offset: Offset(2, 2), color: Colors.black)
|
||||
];
|
||||
|
||||
List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
|
||||
int _minSelected;
|
||||
final GlobalKey<AnimatedListState> miniPlaylistKey = GlobalKey();
|
||||
@ -243,7 +232,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
moonColor: Colors.grey[600],
|
||||
dayColor: Theme.of(context).primaryColorDark,
|
||||
nightColor: Colors.black,
|
||||
onDrag: (value) => audio.setSwitchValue = value,
|
||||
onDrag: (value) {
|
||||
audio.setSwitchValue = value;
|
||||
},
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
audio.sleepTimer(_minSelected);
|
||||
@ -380,7 +371,10 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
BasicPlaybackState
|
||||
.buffering ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState.connecting
|
||||
BasicPlaybackState
|
||||
.connecting ||
|
||||
data.audioState ==
|
||||
BasicPlaybackState.none
|
||||
? 'Buffring...'
|
||||
: '',
|
||||
style: TextStyle(
|
||||
@ -618,7 +612,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
// color: context.primaryColorDark,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Playlist',
|
||||
'Queue',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -654,8 +648,6 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
audio.playNext();
|
||||
miniPlaylistKey.currentState.removeItem(
|
||||
0, (context, animation) => Container());
|
||||
miniPlaylistKey.currentState.removeItem(
|
||||
1, (context, animation) => Container());
|
||||
miniPlaylistKey.currentState.insertItem(0);
|
||||
},
|
||||
child: SizedBox(
|
||||
@ -689,8 +681,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(page: PlaylistPage()),
|
||||
)..then((value) =>
|
||||
miniPlaylistKey.currentState.initState());
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 30.0,
|
||||
@ -723,7 +714,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
itemBuilder: (context, index, animation) => ScaleTransition(
|
||||
alignment: Alignment.center,
|
||||
scale: animation,
|
||||
child: index == 0 || index > data.item1.length -1
|
||||
child: index == 0 || index > data.item1.length - 1
|
||||
? Center()
|
||||
: Column(
|
||||
children: <Widget>[
|
||||
@ -858,7 +849,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
||||
children: <Widget>[
|
||||
TabBarView(
|
||||
children: <Widget>[
|
||||
_sleppMode(context),
|
||||
SleepMode(),
|
||||
_controlPanel(context),
|
||||
_playlist(context),
|
||||
],
|
||||
@ -1120,10 +1111,11 @@ class _LastPositionState extends State<LastPosition> {
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return snapshot.hasData
|
||||
? snapshot.data.seekValue > 0.95
|
||||
? snapshot.data.seekValue > 0.90
|
||||
? Container(
|
||||
height: 20.0,
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.symmetric(horizontal: 5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1,
|
||||
@ -1232,63 +1224,6 @@ class _ImageRotateState extends State<ImageRotate>
|
||||
}
|
||||
}
|
||||
|
||||
class StarSky extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final points = [
|
||||
Offset(50, 100),
|
||||
Offset(150, 75),
|
||||
Offset(250, 250),
|
||||
Offset(130, 200),
|
||||
Offset(270, 150),
|
||||
];
|
||||
final pisces = [
|
||||
Offset(9, 4),
|
||||
Offset(11, 5),
|
||||
Offset(7, 6),
|
||||
Offset(10, 7),
|
||||
Offset(8, 8),
|
||||
Offset(9, 13),
|
||||
Offset(12, 17),
|
||||
Offset(5, 19),
|
||||
Offset(7, 19)
|
||||
].map((e) => e * 10).toList();
|
||||
final orion = [
|
||||
Offset(3, 1),
|
||||
Offset(6, 1),
|
||||
Offset(1, 4),
|
||||
Offset(2, 4),
|
||||
Offset(2, 7),
|
||||
Offset(10, 8),
|
||||
Offset(3, 10),
|
||||
Offset(8, 10),
|
||||
Offset(19, 11),
|
||||
Offset(11, 13),
|
||||
Offset(18, 14),
|
||||
Offset(5, 19),
|
||||
Offset(7, 19),
|
||||
Offset(9, 18),
|
||||
Offset(15, 19),
|
||||
Offset(16, 18),
|
||||
Offset(2, 25),
|
||||
Offset(10, 26)
|
||||
].map((e) => Offset(e.dx * 10 + 250, e.dy * 10)).toList();
|
||||
|
||||
Paint paint = Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
canvas.drawPoints(ui.PointMode.points, pisces, paint);
|
||||
canvas.drawPoints(ui.PointMode.points, points, paint);
|
||||
canvas.drawPoints(ui.PointMode.points, orion, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Meteor extends CustomPainter {
|
||||
Paint _paint;
|
||||
Meteor() {
|
||||
@ -1365,3 +1300,301 @@ class _MeteorLoaderState extends State<MeteorLoader>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SleepMode extends StatefulWidget {
|
||||
SleepMode({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
SleepModeState createState() => SleepModeState();
|
||||
}
|
||||
|
||||
class SleepModeState extends State<SleepMode>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int _minSelected;
|
||||
List minsToSelect = [10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
|
||||
AnimationController _controller;
|
||||
Animation<double> _animation;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_minSelected = 30;
|
||||
_controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
|
||||
_animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
Provider.of<AudioPlayerNotifier>(context, listen: false)
|
||||
.setSwitchValue = _animation.value;
|
||||
});
|
||||
|
||||
_controller.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
Provider.of<AudioPlayerNotifier>(context, listen: false)
|
||||
.sleepTimer(_minSelected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<BoxShadow> customShadow(double scale) => [
|
||||
BoxShadow(
|
||||
blurRadius: 26 * (1 - scale),
|
||||
offset: Offset(-6, -6) * (1 - scale),
|
||||
color: Colors.white),
|
||||
BoxShadow(
|
||||
blurRadius: 8 * (1 - scale),
|
||||
offset: Offset(2, 2) * (1 - scale),
|
||||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
List<BoxShadow> customShadowNight(double scale) => [
|
||||
BoxShadow(
|
||||
blurRadius: 6 * (1 - scale),
|
||||
offset: Offset(-1, -1) * (1 - scale),
|
||||
color: Colors.grey[100].withOpacity(0.3)),
|
||||
BoxShadow(
|
||||
blurRadius: 8 * (1 - scale),
|
||||
offset: Offset(2, 2) * (1 - scale),
|
||||
color: Colors.black)
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ColorTween _colorTween =
|
||||
ColorTween(begin: context.primaryColor, end: Colors.black);
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Selector<AudioPlayerNotifier, Tuple3<int, double, SleepTimerMode>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple3(audio.timeLeft, audio.switchValue, audio.sleepTimerMode),
|
||||
builder: (_, data, __) {
|
||||
double fraction = data.item2 < 0.5 ? data.item2 * 2 : 1;
|
||||
double move = data.item2 > 0.5 ? data.item2 * 2 - 1 : 0;
|
||||
return Container(
|
||||
height: 300,
|
||||
color: _colorTween.transform(move),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.all(5),
|
||||
),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: minsToSelect
|
||||
.map((e) => InkWell(
|
||||
onTap: () => setState(() => _minSelected = e),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: !(e == _minSelected ||
|
||||
fraction > 0)
|
||||
? (Theme.of(context).brightness ==
|
||||
Brightness.dark)
|
||||
? customShadowNight(fraction)
|
||||
: customShadow(fraction)
|
||||
: null,
|
||||
color: (e == _minSelected)
|
||||
? Theme.of(context).accentColor
|
||||
: Theme.of(context).primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
height: 30,
|
||||
width: 30,
|
||||
child: Text(e.toString(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: (e == _minSelected)
|
||||
? Colors.white
|
||||
: null)),
|
||||
),
|
||||
Container(
|
||||
height: 30 * move,
|
||||
width: 30 * move,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
_colorTween.transform(fraction),
|
||||
shape: BoxShape.circle),
|
||||
),
|
||||
],
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 100,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
Positioned(
|
||||
left: data.item3 == SleepTimerMode.timer
|
||||
? -context.width * (move) / 4
|
||||
: context.width * (move) / 4,
|
||||
child: Container(
|
||||
height: 100,
|
||||
width: context.width,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
height: 40,
|
||||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
border:
|
||||
Border.all(color: context.primaryColor),
|
||||
boxShadow:
|
||||
context.brightness == Brightness.light
|
||||
? customShadow(fraction)
|
||||
: customShadowNight(fraction),
|
||||
color: _colorTween.transform(move),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
audio.setSleepTimerMode =
|
||||
SleepTimerMode.endOfEpisode;
|
||||
if (fraction == 0) {
|
||||
_controller.forward();
|
||||
} else if (fraction == 1) {
|
||||
_controller.reverse();
|
||||
audio.cancelTimer();
|
||||
}
|
||||
},
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
width: 120,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'End of episode',
|
||||
style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 20,
|
||||
color: (move > 0
|
||||
? Colors.white
|
||||
: null)),
|
||||
))),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 100 * (1 - fraction),
|
||||
width: 2,
|
||||
color: context.primaryColorDark,
|
||||
),
|
||||
Container(
|
||||
height: 40,
|
||||
width: 120,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
border:
|
||||
Border.all(color: context.primaryColor),
|
||||
boxShadow:
|
||||
context.brightness == Brightness.light
|
||||
? customShadow(fraction)
|
||||
: customShadowNight(fraction),
|
||||
color: _colorTween.transform(move),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
audio.setSleepTimerMode =
|
||||
SleepTimerMode.timer;
|
||||
if (fraction == 0) {
|
||||
_controller.forward();
|
||||
} else if (fraction == 1) {
|
||||
_controller.reverse();
|
||||
audio.cancelTimer();
|
||||
}
|
||||
},
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
width: 120,
|
||||
child: Center(
|
||||
child: Text(
|
||||
data.item2 == 1
|
||||
? _stringForSeconds(
|
||||
data.item1.toDouble())
|
||||
: _stringForSeconds(
|
||||
(_minSelected * 60)
|
||||
.toDouble()),
|
||||
style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// fontSize: 20,
|
||||
color: (move > 0
|
||||
? Colors.white
|
||||
: null)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: 50 + 20 * data.item2,
|
||||
left: context.width / 2 - 100,
|
||||
width: 200,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text('Good Night',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
color: Colors.white.withOpacity(fraction))),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100 * (1 - data.item2) - 30,
|
||||
left: context.width / 2 - 100,
|
||||
width: 200,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text('Sleep Timer',
|
||||
style:
|
||||
TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||
),
|
||||
),
|
||||
data.item2 == 1 ? CustomPaint(painter: StarSky()) : Center(),
|
||||
data.item2 == 1 ? MeteorLoader() : Center(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,81 +51,81 @@ class _DownloadListState extends State<DownloadList> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
padding: EdgeInsets.zero,
|
||||
sliver: Consumer<DownloadState>(builder: (_, downloader, __) {
|
||||
var tasks = downloader.episodeTasks
|
||||
.where((task) => task.status.value != 3)
|
||||
.toList();
|
||||
return tasks.length > 0
|
||||
? SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: tasks[index].episode,
|
||||
)),
|
||||
),
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
child: Text(
|
||||
tasks[index].episode.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
? SliverPadding(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: EpisodeDetail(
|
||||
episodeItem: tasks[index].episode,
|
||||
)),
|
||||
),
|
||||
title: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
child: Text(
|
||||
tasks[index].episode.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: tasks[index].progress >= 0
|
||||
? Container(
|
||||
width: 40.0,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 2),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6)),
|
||||
color: Colors.red),
|
||||
child: Text(
|
||||
tasks[index].progress.toString() + '%',
|
||||
style: TextStyle(color: Colors.white),
|
||||
))
|
||||
: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: SizedBox(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(
|
||||
value: tasks[index].progress / 100,
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: tasks[index].progress >= 0
|
||||
? Container(
|
||||
width: 40.0,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 2),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(6)),
|
||||
color: Colors.red),
|
||||
child: Text(
|
||||
tasks[index].progress.toString() + '%',
|
||||
style: TextStyle(color: Colors.white),
|
||||
))
|
||||
: Container(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: FileImage(
|
||||
File("${tasks[index].episode.imagePath}")),
|
||||
),
|
||||
trailing: _downloadButton(tasks[index], context),
|
||||
);
|
||||
},
|
||||
childCount: tasks.length,
|
||||
subtitle: SizedBox(
|
||||
height: 2,
|
||||
child: LinearProgressIndicator(
|
||||
value: tasks[index].progress / 100,
|
||||
),
|
||||
),
|
||||
leading: CircleAvatar(
|
||||
backgroundImage: FileImage(
|
||||
File("${tasks[index].episode.imagePath}")),
|
||||
),
|
||||
trailing: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
child: _downloadButton(tasks[index], context)),
|
||||
);
|
||||
},
|
||||
childCount: tasks.length,
|
||||
),
|
||||
),
|
||||
)
|
||||
: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return Center();
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
: SliverToBoxAdapter(
|
||||
child: Center(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
@ -18,6 +18,7 @@ import 'package:tsacdop/podcasts/podcastdetail.dart';
|
||||
import 'package:tsacdop/podcasts/podcastmanage.dart';
|
||||
import 'package:tsacdop/util/pageroute.dart';
|
||||
import 'package:tsacdop/util/colorize.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
|
||||
class ScrollPodcasts extends StatefulWidget {
|
||||
@override
|
||||
@ -231,6 +232,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
||||
height: 70,
|
||||
width: _width,
|
||||
alignment: Alignment.centerLeft,
|
||||
color: context.scaffoldBackgroundColor,
|
||||
child: TabBar(
|
||||
labelPadding: EdgeInsets.only(
|
||||
top: 5.0,
|
||||
@ -243,7 +245,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
||||
isScrollable: true,
|
||||
tabs: groups[_groupIndex]
|
||||
.podcasts
|
||||
.map<Tab>((PodcastLocal podcastLocal) {
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Tab(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(
|
||||
@ -403,6 +405,10 @@ class ShowEpisode extends StatelessWidget {
|
||||
final List<EpisodeBrief> episodes;
|
||||
final PodcastLocal podcastLocal;
|
||||
ShowEpisode({Key key, this.episodes, this.podcastLocal}) : super(key: key);
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -597,17 +603,36 @@ class ShowEpisode extends StatelessWidget {
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
(episodes[index].duration)
|
||||
_stringForSeconds(
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble())
|
||||
.toString() +
|
||||
'mins',
|
||||
'|',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
// color: _c,
|
||||
// fontStyle: FontStyle.italic,
|
||||
// color: _c,
|
||||
// fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
episodes[index].enclosureLength != null &&
|
||||
episodes[index].enclosureLength !=
|
||||
0
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
((episodes[index]
|
||||
.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
],
|
||||
)),
|
||||
],
|
||||
|
@ -4,15 +4,19 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart' hide NestedScrollView;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/class/download_state.dart';
|
||||
import 'package:tsacdop/class/podcast_group.dart';
|
||||
import 'package:tsacdop/home/playlist.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:tsacdop/util/episodegrid.dart';
|
||||
import 'package:tsacdop/util/mypopupmenu.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
|
||||
import 'package:tsacdop/home/appbar/importompl.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
@ -328,10 +332,14 @@ class _RecentUpdate extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _RecentUpdateState extends State<_RecentUpdate>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Future<List<EpisodeBrief>> _getRssItem(int top) async {
|
||||
with AutomaticKeepAliveClientMixin , SingleTickerProviderStateMixin{
|
||||
Future<List<EpisodeBrief>> _getRssItem(int top, List<String> group) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = await dbHelper.getRecentRssItem(top);
|
||||
List<EpisodeBrief> episodes;
|
||||
if (group.first == 'All')
|
||||
episodes = await dbHelper.getRecentRssItem(top);
|
||||
else
|
||||
episodes = await dbHelper.getGroupRssItem(top, group);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
@ -347,18 +355,23 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
||||
|
||||
int _top = 99;
|
||||
bool _loadMore;
|
||||
|
||||
String _groupName;
|
||||
List<String> _group;
|
||||
Layout _layout;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadMore = false;
|
||||
_groupName = 'All';
|
||||
_group = ['All'];
|
||||
_layout = Layout.three;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(_top),
|
||||
future: _getRssItem(_top, _group),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
@ -373,8 +386,112 @@ class _RecentUpdateState extends State<_RecentUpdate>
|
||||
key: PageStorageKey<String>('update'),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Consumer<GroupList>(
|
||||
builder: (context, groupList, child) =>
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: PopupMenuButton<String>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10))),
|
||||
elevation: 1,
|
||||
tooltip: 'Groups fliter',
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20),
|
||||
height: 50,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(_groupName),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5),
|
||||
),
|
||||
Icon(
|
||||
LineIcons.filter_solid,
|
||||
size: 18,
|
||||
)
|
||||
],
|
||||
)),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: Text('All'), value: 'All')
|
||||
]..addAll(groupList.groups
|
||||
.map<PopupMenuEntry<String>>((e) =>
|
||||
PopupMenuItem(
|
||||
value: e.name,
|
||||
child: Text(e.name)))
|
||||
.toList()),
|
||||
onSelected: (value) {
|
||||
if (value == 'All') {
|
||||
setState(() {
|
||||
_groupName = 'All';
|
||||
_group = ['All'];
|
||||
});
|
||||
} else {
|
||||
groupList.groups.forEach((group) {
|
||||
if (group.name == value) {
|
||||
setState(() {
|
||||
_groupName = value;
|
||||
_group = group.podcastList;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0,
|
||||
context.textTheme.bodyText1
|
||||
.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1,
|
||||
context.textTheme.bodyText1
|
||||
.color),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
layout: _layout,
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
@ -405,9 +522,9 @@ class _MyFavorite extends StatefulWidget {
|
||||
|
||||
class _MyFavoriteState extends State<_MyFavorite>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Future<List<EpisodeBrief>> _getLikedRssItem(_top) async {
|
||||
Future<List<EpisodeBrief>> _getLikedRssItem(int top, int sortBy) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(_top);
|
||||
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(top, sortBy);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
@ -423,18 +540,21 @@ class _MyFavoriteState extends State<_MyFavorite>
|
||||
|
||||
int _top = 99;
|
||||
bool _loadMore;
|
||||
|
||||
Layout _layout;
|
||||
int _sortBy;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadMore = false;
|
||||
_layout = Layout.three;
|
||||
_sortBy = 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getLikedRssItem(_top),
|
||||
future: _getLikedRssItem(_top, _sortBy),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
@ -448,8 +568,103 @@ class _MyFavoriteState extends State<_MyFavorite>
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>('favorite'),
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10))),
|
||||
elevation: 1,
|
||||
tooltip: 'Sort By',
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('Sory by'),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5),
|
||||
),
|
||||
Icon(
|
||||
_sortBy == 0
|
||||
? LineIcons
|
||||
.cloud_download_alt_solid
|
||||
: LineIcons.heartbeat_solid,
|
||||
size: 18,
|
||||
)
|
||||
],
|
||||
)),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Text('Update Date'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text('Like Date'),
|
||||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
setState(() => _sortBy = 0);
|
||||
else if (value == 1)
|
||||
setState(() => _sortBy = 1);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0,
|
||||
context
|
||||
.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1,
|
||||
context
|
||||
.textTheme.bodyText1.color),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
layout: _layout,
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
@ -481,24 +696,65 @@ class _MyDownload extends StatefulWidget {
|
||||
|
||||
class _MyDownloadState extends State<_MyDownload>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
Layout _layout;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_layout = Layout.three;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return CustomScrollView(
|
||||
key: PageStorageKey<String>('downloas_list'),
|
||||
key: PageStorageKey<String>('download_list'),
|
||||
slivers: <Widget>[
|
||||
DownloadList(),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: EdgeInsets.all(15.0),
|
||||
child: Text('Downloaded'),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 40,
|
||||
color: context.primaryColor,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text('Downloaded')),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0, context.textTheme.bodyText1.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1, context.textTheme.bodyText1.color),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
Consumer<DownloadState>(
|
||||
builder: (_, downloader, __) {
|
||||
@ -511,6 +767,7 @@ class _MyDownloadState extends State<_MyDownload>
|
||||
.toList();
|
||||
return EpisodeGrid(
|
||||
episodes: episodes,
|
||||
layout: _layout,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -520,4 +777,4 @@ class _MyDownloadState extends State<_MyDownload>
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tsacdop/episodes/episodedetail.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
@ -12,7 +11,7 @@ import 'package:line_icons/line_icons.dart';
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
|
||||
class PlaylistPage extends StatefulWidget {
|
||||
@override
|
||||
@ -28,7 +27,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||
return sum;
|
||||
} else {
|
||||
episodes.forEach((episode) {
|
||||
sum += episode.duration;
|
||||
sum += episode.duration ~/ 60;
|
||||
});
|
||||
return sum;
|
||||
}
|
||||
@ -78,7 +77,6 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||
selector: (_, audio) =>
|
||||
Tuple3(audio.queue, audio.playerRunning, audio.queueUpdate),
|
||||
builder: (_, data, __) {
|
||||
print('update');
|
||||
final List<EpisodeBrief> episodes = data.item1.playlist;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -150,15 +148,17 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(5.0),
|
||||
margin: EdgeInsets.only(right: 20.0, bottom: 5.0),
|
||||
decoration: data.item2 ? BoxDecoration(
|
||||
color: context.brightness == Brightness.dark ? Colors.grey[800] : Colors.grey[200],
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10.0)),
|
||||
) :
|
||||
BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.transparent
|
||||
),
|
||||
decoration: data.item2
|
||||
? BoxDecoration(
|
||||
color: context.brightness == Brightness.dark
|
||||
? Colors.grey[800]
|
||||
: Colors.grey[200],
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(10.0)),
|
||||
)
|
||||
: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.transparent),
|
||||
child: data.item2
|
||||
? _topHeight < 90
|
||||
? Row(
|
||||
@ -178,7 +178,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 15,
|
||||
child: WaveLoader()),
|
||||
child: WaveLoader(color: context.accentColor,)),
|
||||
),
|
||||
],
|
||||
)
|
||||
@ -210,13 +210,13 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 15,
|
||||
child: WaveLoader()),
|
||||
child: WaveLoader(color: context.accentColor,)),
|
||||
),
|
||||
],
|
||||
)
|
||||
: IconButton(
|
||||
padding: EdgeInsets.all(0),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(0),
|
||||
alignment: Alignment.center,
|
||||
icon: Icon(Icons.play_circle_filled,
|
||||
size: 40,
|
||||
color: Theme.of(context).accentColor),
|
||||
@ -360,7 +360,8 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
||||
);
|
||||
Scaffold.of(context).showSnackBar(SnackBar(
|
||||
backgroundColor: Colors.grey[800],
|
||||
content: Text('Episode removed', style: TextStyle(color: Colors.white)),
|
||||
content: Text('Episode removed',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
action: SnackBarAction(
|
||||
textColor: context.accentColor,
|
||||
label: 'Undo',
|
||||
@ -403,7 +404,8 @@ class _DismissibleContainerState extends State<DismissibleContainer> {
|
||||
: Center(),
|
||||
widget.episode.duration != 0
|
||||
? _episodeTag(
|
||||
(widget.episode.duration).toString() + 'mins',
|
||||
(widget.episode.duration ~/ 60).toString() +
|
||||
'mins',
|
||||
Colors.cyan[300])
|
||||
: Center(),
|
||||
widget.episode.enclosureLength != null
|
||||
|
@ -21,7 +21,8 @@ class DBHelper {
|
||||
initDb() async {
|
||||
var documentsDirectory = await getDatabasesPath();
|
||||
String path = join(documentsDirectory, "podcasts.db");
|
||||
Database theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
|
||||
Database theDb = await openDatabase(path,
|
||||
version: 1, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||
return theDb;
|
||||
}
|
||||
|
||||
@ -37,16 +38,24 @@ class DBHelper {
|
||||
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
|
||||
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
||||
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
|
||||
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0, media_id TEXT,
|
||||
liked_date INTEGER DEFAULT 0, downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0, media_id TEXT,
|
||||
is_new INTEGER DEFAULT 0)""");
|
||||
await db.execute(
|
||||
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE,
|
||||
seconds REAL, seek_value REAL, add_date INTEGER)""");
|
||||
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT,
|
||||
seconds REAL, seek_value REAL, add_date INTEGER, listen_time INTEGER DEFAULT 0)""");
|
||||
await db.execute(
|
||||
"""CREATE TABLE SubscribeHistory(id TEXT PRIMARY KEY, title TEXT, rss_url TEXT UNIQUE,
|
||||
add_date INTEGER, remove_date INTEGER DEFAULT 0, status INTEGER DEFAULT 0)""");
|
||||
}
|
||||
|
||||
void _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||
if (oldVersion == 1) {
|
||||
await db.execute("ALTER TABLE Episodes ADD liked_date INTEGER DEFAULT 0");
|
||||
await db
|
||||
.execute("ALTER TABLE PlayHistory ADD listen_time INTEGER DEFAULT 0");
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
|
||||
var dbClient = await database;
|
||||
List<PodcastLocal> podcastLocal = List();
|
||||
@ -180,16 +189,24 @@ class DBHelper {
|
||||
Future<int> saveHistory(PlayHistory history) async {
|
||||
var dbClient = await database;
|
||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
List<PlayHistory> 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]);
|
||||
}
|
||||
}
|
||||
int result = await dbClient.transaction((txn) async {
|
||||
return await txn.rawInsert(
|
||||
"""REPLACE INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date)
|
||||
VALUES (?, ?, ?, ?, ?) """,
|
||||
"""REPLACE 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;
|
||||
@ -200,7 +217,7 @@ class DBHelper {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
ORDER BY add_date DESC LIMIT ?
|
||||
""",[top]);
|
||||
""", [top]);
|
||||
List<PlayHistory> playHistory = [];
|
||||
list.forEach((record) {
|
||||
playHistory.add(PlayHistory(record['title'], record['enclosure_url'],
|
||||
@ -210,6 +227,23 @@ class DBHelper {
|
||||
return playHistory;
|
||||
}
|
||||
|
||||
Future<int> isListened(String url) async {
|
||||
var dbClient = await database;
|
||||
int i = 0;
|
||||
List<Map> list =
|
||||
await dbClient.rawQuery("""SELECT listen_time FROM PlayHistory
|
||||
WHERE enclosure_url = ?
|
||||
""", [url]);
|
||||
if (list.length == 0)
|
||||
return 0;
|
||||
else {
|
||||
list.forEach((element) {
|
||||
i += element['listen_time'];
|
||||
});
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SubHistory>> getSubHistory() async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
@ -249,7 +283,8 @@ class DBHelper {
|
||||
Future<PlayHistory> getPosition(EpisodeBrief episodeBrief) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory Where enclosure_url = ?",
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
WHERE enclosure_url = ? ORDER BY add_date DESC LIMIT 1""",
|
||||
[episodeBrief.enclosureUrl]);
|
||||
return list.length > 0
|
||||
? PlayHistory(list.first['title'], list.first['enclosure_url'],
|
||||
@ -358,7 +393,7 @@ class DBHelper {
|
||||
print(pubDate);
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = feed.items[i].itunes.duration?.inMinutes ?? 0;
|
||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||
|
||||
if (url != null) {
|
||||
@ -420,7 +455,7 @@ class DBHelper {
|
||||
final pubDate = feed.items[i].pubDate;
|
||||
final date = _parsePubDate(pubDate);
|
||||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = feed.items[i].itunes.duration?.inMinutes ?? 0;
|
||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||
|
||||
if (url != null) {
|
||||
@ -452,30 +487,55 @@ class DBHelper {
|
||||
return countUpdate - count;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getRssItem(String id, int i) async {
|
||||
Future<List<EpisodeBrief>> getRssItem(String id, int i, bool reverse) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
if (reverse) {
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
|
||||
E.downloaded, P.primaryColor , E.media_id, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds ASC LIMIT ?""", [id, i]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feedTitle'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
}
|
||||
} else {
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
|
||||
E.downloaded, P.primaryColor , E.media_id, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feedTitle'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feedTitle'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
@ -565,38 +625,96 @@ class DBHelper {
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getLikedRssItem(int i) async {
|
||||
Future<List<EpisodeBrief>> getGroupRssItem(
|
||||
int top, List<String> group) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
if (group.length > 0) {
|
||||
List<String> s = group.map<String>((e) => "'$e'").toList();
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||
E.downloaded, P.imagePath, P.primaryColor, E.media_id, E.is_new
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id in (${s.join(',')})
|
||||
ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feed_title'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['doanloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getLikedRssItem(int i, int sortBy) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = List();
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
if (sortBy == 0) {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||
P.primaryColor, E.media_id, E.is_new FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feed_title'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feed_title'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
}
|
||||
} else {
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||
P.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||
P.primaryColor, E.media_id, E.is_new FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.liked = 1 ORDER BY E.liked_date DESC LIMIT ?""", [i]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feed_title'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new']));
|
||||
}
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<int> setLiked(String url) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
int count = await dbClient.rawUpdate(
|
||||
"UPDATE Episodes SET liked = 1 WHERE enclosure_url= ?", [url]);
|
||||
print('liked');
|
||||
"UPDATE Episodes SET liked = 1, liked_date = ? WHERE enclosure_url= ?",
|
||||
[milliseconds, url]);
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -604,10 +722,16 @@ class DBHelper {
|
||||
var dbClient = await database;
|
||||
int count = await dbClient.rawUpdate(
|
||||
"UPDATE Episodes SET liked = 0 WHERE enclosure_url = ?", [url]);
|
||||
print('unliked');
|
||||
return count;
|
||||
}
|
||||
|
||||
Future<bool> isLiked(String url) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("SELECT liked FROM Episodes WHERE enclosure_url = ?", [url]);
|
||||
return list.first['liked'] == 0 ? false : true;
|
||||
}
|
||||
|
||||
Future<int> saveDownloaded(String url, String id) async {
|
||||
var dbClient = await database;
|
||||
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
|
@ -11,6 +11,7 @@ import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:tsacdop/class/podcastlocal.dart';
|
||||
@ -20,6 +21,8 @@ import 'package:tsacdop/util/episodegrid.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
import 'package:tsacdop/class/fireside_data.dart';
|
||||
import 'package:tsacdop/util/colorize.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
|
||||
class PodcastDetail extends StatefulWidget {
|
||||
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
|
||||
@ -36,25 +39,27 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
Future _updateRssItem(PodcastLocal podcastLocal) async {
|
||||
var dbHelper = DBHelper();
|
||||
final result = await dbHelper.updatePodcastRss(podcastLocal);
|
||||
if(result == 0)
|
||||
{ Fluttertoast.showToast(
|
||||
msg: 'No Update',
|
||||
gravity: ToastGravity.TOP,
|
||||
);}
|
||||
else{
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Updated $result Episodes',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
Provider.of<GroupList>(context, listen: false).updatePodcast(podcastLocal);
|
||||
}
|
||||
if (result == 0) {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'No Update',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Updated $result Episodes',
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
Provider.of<GroupList>(context, listen: false)
|
||||
.updatePodcast(podcastLocal);
|
||||
}
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> _getRssItem(
|
||||
PodcastLocal podcastLocal, int i) async {
|
||||
PodcastLocal podcastLocal, int i, bool reverse) async {
|
||||
var dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes = await dbHelper.getRssItem(podcastLocal.id, i);
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getRssItem(podcastLocal.id, i, reverse);
|
||||
if (podcastLocal.provider.contains('fireside')) {
|
||||
FiresideData data = FiresideData(podcastLocal.id, podcastLocal.link);
|
||||
await data.getData();
|
||||
@ -168,12 +173,15 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
ScrollController _controller;
|
||||
int _top;
|
||||
bool _loadMore;
|
||||
|
||||
Layout _layout;
|
||||
bool _reverse;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadMore = false;
|
||||
_top = 99;
|
||||
_layout = Layout.three;
|
||||
_reverse = false;
|
||||
_controller = ScrollController();
|
||||
}
|
||||
|
||||
@ -210,7 +218,8 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal, _top),
|
||||
future:
|
||||
_getRssItem(widget.podcastLocal, _top, _reverse),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
@ -232,7 +241,8 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
});
|
||||
}
|
||||
}),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
physics:
|
||||
const AlwaysScrollableScrollPhysics(),
|
||||
//primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
@ -392,18 +402,121 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
);
|
||||
}),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return hostsList(context, hosts);
|
||||
},
|
||||
childCount: 1,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: hostsList(context, hosts),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.all(
|
||||
Radius.circular(
|
||||
10))),
|
||||
elevation: 1,
|
||||
tooltip: 'Sort By',
|
||||
child: Container(
|
||||
height: 30,
|
||||
padding:
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text('Sort by'),
|
||||
Padding(
|
||||
padding: EdgeInsets
|
||||
.symmetric(
|
||||
horizontal:
|
||||
5),
|
||||
),
|
||||
Icon(
|
||||
_reverse
|
||||
? LineIcons
|
||||
.hourglass_start_solid
|
||||
: LineIcons
|
||||
.hourglass_end_solid,
|
||||
size: 18,
|
||||
)
|
||||
],
|
||||
)),
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
child: Text('Newest first'),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
child: Text('Oldest first'),
|
||||
)
|
||||
],
|
||||
onSelected: (value) {
|
||||
if (value == 0)
|
||||
setState(() =>
|
||||
_reverse = false);
|
||||
else if (value == 1)
|
||||
setState(() =>
|
||||
_reverse = true);
|
||||
},
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
if (_layout == Layout.three)
|
||||
setState(() {
|
||||
_layout = Layout.two;
|
||||
});
|
||||
else
|
||||
setState(() {
|
||||
_layout = Layout.three;
|
||||
});
|
||||
},
|
||||
icon: _layout == Layout.three
|
||||
? SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
0,
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color),
|
||||
),
|
||||
)
|
||||
: SizedBox(
|
||||
height: 10,
|
||||
width: 30,
|
||||
child: CustomPaint(
|
||||
painter: LayoutPainter(
|
||||
1,
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
EpisodeGrid(
|
||||
episodes: snapshot.data,
|
||||
showFavorite: true,
|
||||
showNumber: true,
|
||||
layout: _layout,
|
||||
reverse: _reverse,
|
||||
episodeCount:
|
||||
widget.podcastLocal.episodeCount,
|
||||
),
|
||||
@ -519,12 +632,6 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Linkify(
|
||||
@ -554,3 +661,4 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,12 +162,6 @@ class _PodcastListState extends State<PodcastList> {
|
||||
113, 113, 113, 1)
|
||||
: Color.fromRGBO(
|
||||
15, 15, 15, 1),
|
||||
// statusBarColor: Theme.of(context)
|
||||
// .brightness ==
|
||||
// Brightness.light
|
||||
// ? Color.fromRGBO(
|
||||
// 113, 113, 113, 1)
|
||||
// : Color.fromRGBO(5, 5, 5, 1),
|
||||
),
|
||||
child: AboutPodcast(
|
||||
podcastLocal:
|
||||
|
@ -10,6 +10,7 @@ import 'package:tsacdop/class/podcast_group.dart';
|
||||
import 'package:tsacdop/podcasts/podcastgroup.dart';
|
||||
import 'package:tsacdop/podcasts/podcastlist.dart';
|
||||
import 'package:tsacdop/util/pageroute.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'custom_tabview.dart';
|
||||
|
||||
class PodcastManage extends StatefulWidget {
|
||||
@ -173,31 +174,34 @@ class _PodcastManageState extends State<PodcastManage>
|
||||
? Center()
|
||||
: Stack(
|
||||
children: <Widget>[
|
||||
CustomTabView(
|
||||
itemCount: _groups.length,
|
||||
tabBuilder: (context, index) => Tab(
|
||||
child: Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: (_scroll - index).abs() > 1
|
||||
? Colors.grey[300]
|
||||
: Colors.grey[300]
|
||||
.withOpacity((_scroll - index).abs()),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(15)),
|
||||
),
|
||||
child: Text(
|
||||
_groups[index].name,
|
||||
)),
|
||||
Container(
|
||||
color: context.scaffoldBackgroundColor,
|
||||
child: CustomTabView(
|
||||
itemCount: _groups.length,
|
||||
tabBuilder: (context, index) => Tab(
|
||||
child: Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: (_scroll - index).abs() > 1
|
||||
? Colors.grey[300]
|
||||
: Colors.grey[300]
|
||||
.withOpacity((_scroll - index).abs()),
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(15)),
|
||||
),
|
||||
child: Text(
|
||||
_groups[index].name,
|
||||
)),
|
||||
),
|
||||
pageBuilder: (context, index) => Container(
|
||||
key: ValueKey(_groups[index].name),
|
||||
child: PodcastGroupList(group: _groups[index])),
|
||||
onPositionChange: (value) =>
|
||||
setState(() => _index = value),
|
||||
onScroll: (value) => setState(() => _scroll = value),
|
||||
),
|
||||
pageBuilder: (context, index) => Container(
|
||||
key: ValueKey(_groups[index].name),
|
||||
child: PodcastGroupList(group: _groups[index])),
|
||||
onPositionChange: (value) =>
|
||||
setState(() => _index = value),
|
||||
onScroll: (value) => setState(() => _scroll = value),
|
||||
),
|
||||
_showSetting
|
||||
? Positioned.fill(
|
||||
|
@ -285,7 +285,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
||||
.data[index].subDate)
|
||||
.inDays
|
||||
.toString() +
|
||||
' days')
|
||||
' days ago')
|
||||
: Text(snapshot.data[index].delDate
|
||||
.difference(snapshot
|
||||
.data[index].subDate)
|
||||
|
@ -46,4 +46,5 @@ List<Libries> plugins = [
|
||||
Libries('flutter_linkify', mit, 'https://pub.dev/packages/flutter_linkify'),
|
||||
Libries('extended_nested_scroll_view', mit, 'https://pub.dev/packages/extended_nested_scroll_view'),
|
||||
Libries('connectivity', bsd, 'https://pub.dev/packages/connectivity'),
|
||||
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart')
|
||||
];
|
476
lib/util/custompaint.dart
Normal file
476
lib/util/custompaint.dart
Normal file
@ -0,0 +1,476 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
//Layout change indicator
|
||||
class LayoutPainter extends CustomPainter {
|
||||
double scale;
|
||||
Color color;
|
||||
LayoutPainter(this.scale, this.color);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.0
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
canvas.drawRect(Rect.fromLTRB(0, 0, 10 + 5 * scale, 10), _paint);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(10 + 5 * scale, 0, 20 + 10 * scale, 10), _paint);
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(20 + 5 * scale, 0, 30, 10 - 10 * scale), _paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Dark sky used in sleep timer
|
||||
class StarSky extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final points = [
|
||||
Offset(50, 100),
|
||||
Offset(150, 75),
|
||||
Offset(250, 250),
|
||||
Offset(130, 200),
|
||||
Offset(270, 150),
|
||||
];
|
||||
final pisces = [
|
||||
Offset(9, 4),
|
||||
Offset(11, 5),
|
||||
Offset(7, 6),
|
||||
Offset(10, 7),
|
||||
Offset(8, 8),
|
||||
Offset(9, 13),
|
||||
Offset(12, 17),
|
||||
Offset(5, 19),
|
||||
Offset(7, 19)
|
||||
].map((e) => e * 10).toList();
|
||||
final orion = [
|
||||
Offset(3, 1),
|
||||
Offset(6, 1),
|
||||
Offset(1, 4),
|
||||
Offset(2, 4),
|
||||
Offset(2, 7),
|
||||
Offset(10, 8),
|
||||
Offset(3, 10),
|
||||
Offset(8, 10),
|
||||
Offset(19, 11),
|
||||
Offset(11, 13),
|
||||
Offset(18, 14),
|
||||
Offset(5, 19),
|
||||
Offset(7, 19),
|
||||
Offset(9, 18),
|
||||
Offset(15, 19),
|
||||
Offset(16, 18),
|
||||
Offset(2, 25),
|
||||
Offset(10, 26)
|
||||
].map((e) => Offset(e.dx * 10 + 250, e.dy * 10)).toList();
|
||||
|
||||
Paint paint = Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = 2.0
|
||||
..strokeCap = StrokeCap.round;
|
||||
canvas.drawPoints(ui.PointMode.points, pisces, paint);
|
||||
canvas.drawPoints(ui.PointMode.points, points, paint);
|
||||
canvas.drawPoints(ui.PointMode.points, orion, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Listened indicator
|
||||
class ListenedPainter extends CustomPainter {
|
||||
Color _color;
|
||||
double stroke;
|
||||
ListenedPainter(this._color,{this.stroke = 1.0});
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
..strokeWidth = stroke
|
||||
..strokeCap = StrokeCap.round
|
||||
..style = PaintingStyle.stroke;
|
||||
Path _path = Path();
|
||||
|
||||
_path.moveTo(size.width / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width / 6, size.height * 5 / 8);
|
||||
_path.moveTo(size.width / 3, size.height / 4);
|
||||
_path.lineTo(size.width / 3, size.height * 3 / 4);
|
||||
_path.moveTo(size.width / 2, size.height / 8);
|
||||
_path.lineTo(size.width / 2, size.height * 7 / 8);
|
||||
_path.moveTo(size.width * 5 / 6, size.height * 3 / 8);
|
||||
_path.lineTo(size.width * 5 / 6, size.height * 5 / 8);
|
||||
_path.moveTo(size.width * 2 / 3, size.height / 4);
|
||||
_path.lineTo(size.width * 2 / 3, size.height * 3 / 4);
|
||||
|
||||
canvas.drawPath(_path, _paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Wave play indicator
|
||||
class WavePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
double _value;
|
||||
Color _color;
|
||||
WavePainter(this._fraction, this._color);
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (_fraction < 0.5) {
|
||||
_value = _fraction;
|
||||
} else {
|
||||
_value = 1 - _fraction;
|
||||
}
|
||||
Path _path = Path();
|
||||
Paint _paint = Paint()
|
||||
..color = _color
|
||||
..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 {
|
||||
final Color color;
|
||||
WaveLoader({this.color, Key key}) : super(key: key);
|
||||
@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, widget.color ?? Colors.white));
|
||||
}
|
||||
}
|
||||
|
||||
//Love shape
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//Line buffer indicator
|
||||
//Not used
|
||||
class LinePainter extends CustomPainter {
|
||||
double _fraction;
|
||||
Paint _paint;
|
||||
Color _maincolor;
|
||||
LinePainter(this._fraction, this._maincolor) {
|
||||
_paint = Paint()
|
||||
..color = _maincolor
|
||||
..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();
|
||||
controller =
|
||||
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
|
||||
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, Theme.of(context).accentColor));
|
||||
}
|
||||
}
|
||||
|
||||
class ImageRotate extends StatefulWidget {
|
||||
final String title;
|
||||
final String path;
|
||||
ImageRotate({this.title, this.path, Key key}) : super(key: key);
|
||||
@override
|
||||
_ImageRotateState createState() => _ImageRotateState();
|
||||
}
|
||||
|
||||
class _ImageRotateState extends State<ImageRotate>
|
||||
with SingleTickerProviderStateMixin {
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
double _value;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = 0;
|
||||
_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,
|
||||
child: Image.file(File("${widget.path}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
duration: Duration(milliseconds: 300),
|
||||
);
|
||||
|
||||
_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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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(
|
||||
angle: angle,
|
||||
child: SizedBox(
|
||||
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>[
|
||||
_littleHeart(0.5, 10, -math.pi / 6),
|
||||
_littleHeart(1.2, 3, 0),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
_littleHeart(0.8, 6, math.pi * 1.5),
|
||||
_littleHeart(0.9, 24, math.pi / 2),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_littleHeart(1, 8, -math.pi * 0.7),
|
||||
_littleHeart(0.8, 8, math.pi),
|
||||
_littleHeart(0.6, 3, -math.pi * 1.2)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -13,6 +13,11 @@ import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/episodes/episodedetail.dart';
|
||||
import 'package:tsacdop/util/colorize.dart';
|
||||
import 'package:tsacdop/util/context_extension.dart';
|
||||
import 'package:tsacdop/util/custompaint.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
|
||||
enum Layout { two, three }
|
||||
|
||||
class EpisodeGrid extends StatelessWidget {
|
||||
final List<EpisodeBrief> episodes;
|
||||
@ -20,15 +25,33 @@ class EpisodeGrid extends StatelessWidget {
|
||||
final bool showDownload;
|
||||
final bool showNumber;
|
||||
final int episodeCount;
|
||||
EpisodeGrid(
|
||||
{Key key,
|
||||
@required this.episodes,
|
||||
this.showDownload = false,
|
||||
this.showFavorite = false,
|
||||
this.showNumber = false,
|
||||
this.episodeCount = 0
|
||||
})
|
||||
: super(key: key);
|
||||
final Layout layout;
|
||||
final bool reverse;
|
||||
Future<int> _isListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isListened(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
Future<bool> _isLiked(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
return await dbHelper.isLiked(episode.enclosureUrl);
|
||||
}
|
||||
|
||||
String _stringForSeconds(double seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
EpisodeGrid({
|
||||
Key key,
|
||||
@required this.episodes,
|
||||
this.showDownload = false,
|
||||
this.showFavorite = false,
|
||||
this.showNumber = false,
|
||||
this.episodeCount = 0,
|
||||
this.layout = Layout.three,
|
||||
this.reverse,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -100,12 +123,12 @@ class EpisodeGrid extends StatelessWidget {
|
||||
}
|
||||
|
||||
return SliverPadding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10.0, bottom: 5.0, left: 15.0, right: 15.0),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
childAspectRatio: 1,
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: layout == Layout.three ? 1 : 1.5,
|
||||
crossAxisCount: layout == Layout.three ? 3 : 2,
|
||||
mainAxisSpacing: 6.0,
|
||||
crossAxisSpacing: 6.0,
|
||||
),
|
||||
@ -120,147 +143,291 @@ class EpisodeGrid extends StatelessWidget {
|
||||
audio.queue.playlist.map((e) => e.enclosureUrl).toList()),
|
||||
builder: (_, data, __) => OpenContainerWrapper(
|
||||
episode: episodes[index],
|
||||
closedBuilder: (context, action, boo) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).primaryColor,
|
||||
blurRadius: 0.5,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
onTapDown: (details) => _offset = Offset(
|
||||
details.globalPosition.dx, details.globalPosition.dy),
|
||||
onLongPress: () => _showPopupMenu(
|
||||
_offset,
|
||||
episodes[index],
|
||||
context,
|
||||
data.item1 == episodes[index],
|
||||
data.item2.contains(episodes[index].enclosureUrl)),
|
||||
onTap: action,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
closedBuilder: (context, action, boo) => FutureBuilder<int>(
|
||||
future: _isListened(episodes[index]),
|
||||
initialData: 0,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
border: Border.all(
|
||||
color:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: _width / 16,
|
||||
width: _width / 16,
|
||||
child: boo
|
||||
? Center()
|
||||
: CircleAvatar(
|
||||
backgroundColor:
|
||||
_c.withOpacity(0.5),
|
||||
backgroundImage: FileImage(File(
|
||||
"${episodes[index].imagePath}")),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
episodes[index].isNew == 1
|
||||
? Text('New',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontStyle: FontStyle.italic))
|
||||
: Center(),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
showNumber
|
||||
? Container(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text(
|
||||
(episodeCount - index).toString(),
|
||||
style: GoogleFonts.teko(
|
||||
textStyle: TextStyle(
|
||||
fontSize: _width / 24,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
],
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(5.0)),
|
||||
color: snapshot.data > 0
|
||||
? context.brightness == Brightness.light
|
||||
? context.primaryColor
|
||||
: Color.fromRGBO(40, 40, 40, 1)
|
||||
: context.scaffoldBackgroundColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: context.brightness == Brightness.light
|
||||
? context.primaryColor
|
||||
: Color.fromRGBO(40, 40, 40, 1),
|
||||
blurRadius: 0.5,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: EdgeInsets.only(top: 2.0),
|
||||
child: Text(
|
||||
episodes[index].title,
|
||||
style: TextStyle(
|
||||
// fontSize: _width / 32,
|
||||
),
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.fade,
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(5.0)),
|
||||
onTapDown: (details) => _offset = Offset(
|
||||
details.globalPosition.dx,
|
||||
details.globalPosition.dy),
|
||||
onLongPress: () => _showPopupMenu(
|
||||
_offset,
|
||||
episodes[index],
|
||||
context,
|
||||
data.item1 == episodes[index],
|
||||
data.item2
|
||||
.contains(episodes[index].enclosureUrl)),
|
||||
onTap: action,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(5.0)),
|
||||
border: Border.all(
|
||||
color: context.brightness == Brightness.light
|
||||
? context.primaryColor
|
||||
: context.scaffoldBackgroundColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
episodes[index].dateToString(),
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
color: _c,
|
||||
fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(1),
|
||||
),
|
||||
showFavorite
|
||||
? Container(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: (episodes[index].liked == 0)
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: _width / 16,
|
||||
width: _width / 16,
|
||||
child: boo
|
||||
? Center()
|
||||
: IconTheme(
|
||||
data: IconThemeData(size: 15),
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
: CircleAvatar(
|
||||
backgroundColor:
|
||||
_c.withOpacity(0.5),
|
||||
backgroundImage: FileImage(File(
|
||||
"${episodes[index].imagePath}")),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
episodes[index].isNew == 1
|
||||
? Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 2),
|
||||
child: Text('New',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontStyle:
|
||||
FontStyle.italic)),
|
||||
)
|
||||
: Center(),
|
||||
Selector<AudioPlayerNotifier,
|
||||
EpisodeBrief>(
|
||||
selector: (_, audio) =>
|
||||
audio.episode,
|
||||
builder: (_, data, __) {
|
||||
return (episodes[index]
|
||||
.enclosureUrl ==
|
||||
data?.enclosureUrl)
|
||||
? Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin:
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 8,
|
||||
width: 15,
|
||||
child: WaveLoader(
|
||||
color: context
|
||||
.accentColor)))
|
||||
: layout == Layout.two &&
|
||||
snapshot.data > 0
|
||||
? Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin: EdgeInsets
|
||||
.symmetric(
|
||||
horizontal:
|
||||
2),
|
||||
decoration:
|
||||
BoxDecoration(
|
||||
color: context
|
||||
.accentColor,
|
||||
shape:
|
||||
BoxShape.circle,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 10,
|
||||
width: 15,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
ListenedPainter(
|
||||
Colors.white,
|
||||
)),
|
||||
))
|
||||
: Center();
|
||||
}),
|
||||
showDownload || layout == Layout.two
|
||||
? Container(
|
||||
child: (episodes[index]
|
||||
.enclosureUrl !=
|
||||
episodes[index].mediaId)
|
||||
? Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin: EdgeInsets
|
||||
.symmetric(
|
||||
horizontal: 5),
|
||||
padding: EdgeInsets
|
||||
.symmetric(
|
||||
horizontal: 2),
|
||||
decoration:
|
||||
BoxDecoration(
|
||||
color: context
|
||||
.accentColor,
|
||||
shape:
|
||||
BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.done_all,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
)
|
||||
: Center(),
|
||||
showNumber
|
||||
? Container(
|
||||
alignment: Alignment.topRight,
|
||||
child: Text(
|
||||
reverse
|
||||
? (index + 1).toString()
|
||||
: (episodeCount - index)
|
||||
.toString(),
|
||||
style: GoogleFonts.teko(
|
||||
textStyle: TextStyle(
|
||||
fontSize: _width / 24,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
)
|
||||
: Center(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: EdgeInsets.only(top: 2.0),
|
||||
child: Text(
|
||||
episodes[index].title,
|
||||
style: TextStyle(
|
||||
// fontSize: _width / 32,
|
||||
),
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.fade,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: Text(
|
||||
episodes[index].dateToString(),
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35,
|
||||
color: _c,
|
||||
fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
layout == Layout.two &&
|
||||
episodes[index].duration != 0
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_stringForSeconds(
|
||||
episodes[index]
|
||||
.duration
|
||||
.toDouble()) +
|
||||
'|',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
layout == Layout.two &&
|
||||
episodes[index]
|
||||
.enclosureLength !=
|
||||
null &&
|
||||
episodes[index]
|
||||
.enclosureLength !=
|
||||
0
|
||||
? Container(
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
((episodes[index]
|
||||
.enclosureLength) ~/
|
||||
1000000)
|
||||
.toString() +
|
||||
'MB',
|
||||
style: TextStyle(
|
||||
fontSize: _width / 35),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(1),
|
||||
),
|
||||
showFavorite || layout == Layout.two
|
||||
? FutureBuilder<bool>(
|
||||
future:
|
||||
_isLiked(episodes[index]),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) =>
|
||||
Container(
|
||||
alignment:
|
||||
Alignment.bottomRight,
|
||||
child: (snapshot.data)
|
||||
? IconTheme(
|
||||
data: IconThemeData(
|
||||
size: 15),
|
||||
child: Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
),
|
||||
)
|
||||
: Center(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -271,7 +438,6 @@ class EpisodeGrid extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OpenContainerWrapper extends StatelessWidget {
|
||||
const OpenContainerWrapper({
|
||||
this.closedBuilder,
|
||||
@ -318,5 +484,3 @@ class OpenContainerWrapper extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,6 +64,7 @@ dev_dependencies:
|
||||
extended_nested_scroll_view: ^0.4.0
|
||||
connectivity: ^0.4.8+2
|
||||
flare_flutter: ^2.0.1
|
||||
rxdart: ^0.23.1
|
||||
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
|
Loading…
x
Reference in New Issue
Block a user