mirror of
https://github.com/stonega/tsacdop
synced 2025-02-16 11:31:45 +01:00
A lot of bug fixed
This commit is contained in:
parent
62100085b0
commit
a1d004aa43
@ -2,7 +2,7 @@
|
|||||||
[![CircleCI](https://circleci.com/gh/stonega/tsacdop.svg?style=svg)](https://circleci.com/gh/stonega/workflows/tsacdop/)
|
[![CircleCI](https://circleci.com/gh/stonega/tsacdop.svg?style=svg)](https://circleci.com/gh/stonega/workflows/tsacdop/)
|
||||||
## About
|
## About
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png" art = "Logo"/>
|
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-xhdpi/ic_notification.png" art = "Logo"/>
|
||||||
</br>
|
</br>
|
||||||
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-xhdpi/text.png" art = "Tsacdop"/>
|
<img src="https://raw.githubusercontent.com/stonega/tsacdop/master/android/app/src/main/res/mipmap-xhdpi/text.png" art = "Tsacdop"/>
|
||||||
</p>
|
</p>
|
||||||
@ -18,7 +18,7 @@ The podcasts search engine is powered by [ListenNotes](https://listennotes.com).
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Tsacdop is licensed under the [MIT](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
|
Tsacdop is licensed under the [GPL V3.0](https://github.com/stonega/tsacdop/blob/master/LICENSE) license.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
|
4
android/app/.settings/org.eclipse.jdt.core.prefs
Normal file
4
android/app/.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.8
|
@ -59,12 +59,10 @@ class PlayHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Playlist {
|
class Playlist extends ChangeNotifier {
|
||||||
String name;
|
String name;
|
||||||
DBHelper dbHelper = DBHelper();
|
DBHelper dbHelper = DBHelper();
|
||||||
// list of urls
|
|
||||||
//List<String> _urls;
|
|
||||||
//list of episodes
|
|
||||||
List<EpisodeBrief> _playlist;
|
List<EpisodeBrief> _playlist;
|
||||||
//list of miediaitem
|
//list of miediaitem
|
||||||
|
|
||||||
@ -73,14 +71,13 @@ class Playlist {
|
|||||||
|
|
||||||
getPlaylist() async {
|
getPlaylist() async {
|
||||||
List<String> urls = await storage.getStringList();
|
List<String> urls = await storage.getStringList();
|
||||||
print(urls);
|
|
||||||
if (urls.length == 0) {
|
if (urls.length == 0) {
|
||||||
_playlist = [];
|
_playlist = [];
|
||||||
} else {
|
} else {
|
||||||
_playlist = [];
|
_playlist = [];
|
||||||
await Future.forEach(urls, (url) async {
|
await Future.forEach(urls, (url) async {
|
||||||
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
||||||
if(episode != null) _playlist.add(episode);
|
if (episode != null) _playlist.add(episode);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
print('Playlist: ' + _playlist.length.toString());
|
print('Playlist: ' + _playlist.length.toString());
|
||||||
@ -89,7 +86,6 @@ class Playlist {
|
|||||||
savePlaylist() async {
|
savePlaylist() async {
|
||||||
List<String> urls = [];
|
List<String> urls = [];
|
||||||
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
||||||
print(urls);
|
|
||||||
await storage.saveStringList(urls);
|
await storage.saveStringList(urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,10 +99,12 @@ class Playlist {
|
|||||||
await savePlaylist();
|
await savePlaylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
delFromPlaylist(EpisodeBrief episodeBrief) async {
|
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
|
||||||
|
int index = _playlist.indexOf(episodeBrief);
|
||||||
_playlist
|
_playlist
|
||||||
.removeWhere((item) => item.enclosureUrl == episodeBrief.enclosureUrl);
|
.removeWhere((item) => item.enclosureUrl == episodeBrief.enclosureUrl);
|
||||||
await savePlaylist();
|
await savePlaylist();
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +113,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
KeyValueStorage storage = KeyValueStorage('audioposition');
|
KeyValueStorage storage = KeyValueStorage('audioposition');
|
||||||
EpisodeBrief _episode;
|
EpisodeBrief _episode;
|
||||||
Playlist _queue = Playlist();
|
Playlist _queue = Playlist();
|
||||||
|
bool _queueUpdate = false;
|
||||||
BasicPlaybackState _audioState = BasicPlaybackState.none;
|
BasicPlaybackState _audioState = BasicPlaybackState.none;
|
||||||
bool _playerRunning = false;
|
bool _playerRunning = false;
|
||||||
bool _noSlide = true;
|
bool _noSlide = true;
|
||||||
@ -127,7 +126,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
bool _stopOnComplete = false;
|
bool _stopOnComplete = false;
|
||||||
Timer _stopTimer;
|
Timer _stopTimer;
|
||||||
int _timeLeft = 0;
|
int _timeLeft = 0;
|
||||||
//Show stopwatch after user setting timer.
|
|
||||||
bool _showStopWatch = false;
|
bool _showStopWatch = false;
|
||||||
double _switchValue = 0;
|
double _switchValue = 0;
|
||||||
bool _autoPlay = true;
|
bool _autoPlay = true;
|
||||||
@ -143,6 +141,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
bool get playerRunning => _playerRunning;
|
bool get playerRunning => _playerRunning;
|
||||||
int get lastPositin => _lastPostion;
|
int get lastPositin => _lastPostion;
|
||||||
Playlist get queue => _queue;
|
Playlist get queue => _queue;
|
||||||
|
bool get queueUpdate => _queueUpdate;
|
||||||
EpisodeBrief get episode => _episode;
|
EpisodeBrief get episode => _episode;
|
||||||
bool get stopOnComplete => _stopOnComplete;
|
bool get stopOnComplete => _stopOnComplete;
|
||||||
bool get showStopWatch => _showStopWatch;
|
bool get showStopWatch => _showStopWatch;
|
||||||
@ -155,7 +154,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
set autoPlaySwitch(bool boo) {
|
set autoPlaySwitch(bool boo) {
|
||||||
_autoPlay = boo;
|
_autoPlay = boo;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@ -163,38 +162,50 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
void addListener(VoidCallback listener) async {
|
void addListener(VoidCallback listener) async {
|
||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
|
_queueUpdate = false;
|
||||||
await AudioService.connect();
|
await AudioService.connect();
|
||||||
if(await AudioService.running){
|
bool running = await AudioService.running;
|
||||||
AudioService.stop();
|
if (running) {
|
||||||
|
await AudioService.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPlaylist() async {
|
loadPlaylist() async {
|
||||||
await _queue.getPlaylist();
|
await _queue.getPlaylist();
|
||||||
_lastPostion = await storage.getInt();
|
_lastPostion = await storage.getInt();
|
||||||
|
if (_lastPostion > 0 && _queue.playlist.length > 0) {
|
||||||
|
final EpisodeBrief episode = _queue.playlist.first;
|
||||||
|
final int duration = episode.enclosureLength * 60;
|
||||||
|
final double seekValue = duration != 0 ? _lastPostion / duration : 1;
|
||||||
|
final PlayHistory history = PlayHistory(
|
||||||
|
episode.title, episode.enclosureUrl, _lastPostion / 1000, seekValue);
|
||||||
|
await dbHelper.saveHistory(history);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeLoad(EpisodeBrief episode) async {
|
episodeLoad(EpisodeBrief episode) async {
|
||||||
|
final EpisodeBrief episodeNew =
|
||||||
|
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||||
if (_playerRunning) {
|
if (_playerRunning) {
|
||||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||||
backgroundAudioPosition / 1000, seekSliderValue);
|
backgroundAudioPosition / 1000, seekSliderValue);
|
||||||
await dbHelper.saveHistory(history);
|
await dbHelper.saveHistory(history);
|
||||||
AudioService.addQueueItemAt(episode.toMediaItem(), 0);
|
AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0);
|
||||||
_queue.playlist
|
_queue.playlist
|
||||||
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||||
_queue.playlist.insert(0, episode);
|
_queue.playlist.insert(0, episodeNew);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await _queue.savePlaylist();
|
await _queue.savePlaylist();
|
||||||
} else {
|
} else {
|
||||||
await _queue.getPlaylist();
|
await _queue.getPlaylist();
|
||||||
_queue.playlist
|
_queue.playlist
|
||||||
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
.removeWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||||
_queue.playlist.insert(0, episode);
|
_queue.playlist.insert(0, episodeNew);
|
||||||
_queue.savePlaylist();
|
_queue.savePlaylist();
|
||||||
_backgroundAudioDuration = 0;
|
_backgroundAudioDuration = 0;
|
||||||
_backgroundAudioPosition = 0;
|
_backgroundAudioPosition = 0;
|
||||||
_seekSliderValue = 0;
|
_seekSliderValue = 0;
|
||||||
_episode = episode;
|
_episode = episodeNew;
|
||||||
_playerRunning = true;
|
_playerRunning = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
await _queue.savePlaylist();
|
await _queue.savePlaylist();
|
||||||
@ -207,13 +218,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
await AudioService.connect();
|
await AudioService.connect();
|
||||||
}
|
}
|
||||||
await AudioService.start(
|
await AudioService.start(
|
||||||
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
|
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
|
||||||
androidNotificationChannelName: 'Tsacdop',
|
androidNotificationChannelName: 'Tsacdop',
|
||||||
notificationColor: 0xFF2196f3,
|
notificationColor: 0xFF4d91be,
|
||||||
androidNotificationIcon: 'drawable/ic_notification',
|
androidNotificationIcon: 'drawable/ic_notification',
|
||||||
enableQueue: true,
|
enableQueue: true,
|
||||||
androidStopOnRemoveTask: true,
|
androidStopOnRemoveTask: true,
|
||||||
);
|
androidStopForegroundOnPause: true);
|
||||||
_playerRunning = true;
|
_playerRunning = true;
|
||||||
if (_autoPlay) {
|
if (_autoPlay) {
|
||||||
await Future.forEach(_queue.playlist, (episode) async {
|
await Future.forEach(_queue.playlist, (episode) async {
|
||||||
@ -242,45 +253,58 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
print(_episode.title);
|
print(_episode.title);
|
||||||
_queue.delFromPlaylist(_episode);
|
_queue.delFromPlaylist(_episode);
|
||||||
}
|
}
|
||||||
if (_audioState == BasicPlaybackState.paused ||
|
if (_audioState == BasicPlaybackState.skippingToNext &&
|
||||||
_audioState == BasicPlaybackState.skippingToNext &&
|
_episode != null &&
|
||||||
_episode != null) {
|
_backgroundAudioPosition > 0) {
|
||||||
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||||
backgroundAudioPosition / 1000, seekSliderValue);
|
_backgroundAudioPosition / 1000, _seekSliderValue);
|
||||||
await dbHelper.saveHistory(history);
|
await dbHelper.saveHistory(history);
|
||||||
}
|
}
|
||||||
if (_audioState == BasicPlaybackState.stopped) {
|
if (_audioState == BasicPlaybackState.stopped) _playerRunning = false;
|
||||||
_playerRunning = false;
|
|
||||||
|
if (_audioState == BasicPlaybackState.error) {
|
||||||
|
_remoteErrorMessage = 'Network Error';
|
||||||
}
|
}
|
||||||
|
if (_audioState != BasicPlaybackState.error &&
|
||||||
|
_audioState != BasicPlaybackState.paused) {
|
||||||
|
_remoteErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
_currentPosition = event?.currentPosition ?? 0;
|
_currentPosition = event?.currentPosition ?? 0;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
Timer.periodic(Duration(milliseconds: 500), (timer) {
|
||||||
if (_noSlide) {
|
if (_noSlide) {
|
||||||
_audioState == BasicPlaybackState.playing
|
if (_audioState == BasicPlaybackState.playing) {
|
||||||
? (_backgroundAudioPosition < _backgroundAudioDuration - 500)
|
if (_backgroundAudioPosition < _backgroundAudioDuration - 500)
|
||||||
? _backgroundAudioPosition = _currentPosition +
|
_backgroundAudioPosition = _currentPosition +
|
||||||
DateTime.now().difference(_current).inMilliseconds
|
DateTime.now().difference(_current).inMilliseconds;
|
||||||
: _backgroundAudioPosition = _backgroundAudioDuration
|
else
|
||||||
: _backgroundAudioPosition = _currentPosition;
|
_backgroundAudioPosition = _backgroundAudioDuration;
|
||||||
|
} else
|
||||||
|
_backgroundAudioPosition = _currentPosition;
|
||||||
|
|
||||||
if (_backgroundAudioDuration != null &&
|
if (_backgroundAudioDuration != null &&
|
||||||
_backgroundAudioDuration != 0 &&
|
_backgroundAudioDuration != 0 &&
|
||||||
_backgroundAudioPosition != null) {
|
_backgroundAudioPosition != null) {
|
||||||
_seekSliderValue =
|
_seekSliderValue =
|
||||||
_backgroundAudioPosition / _backgroundAudioDuration ?? 0;
|
_backgroundAudioPosition / _backgroundAudioDuration ?? 0;
|
||||||
}
|
} else
|
||||||
|
_seekSliderValue = 0;
|
||||||
|
|
||||||
if (_backgroundAudioPosition > 0) {
|
if (_backgroundAudioPosition > 0) {
|
||||||
_lastPostion = _backgroundAudioPosition;
|
_lastPostion = _backgroundAudioPosition;
|
||||||
storage.saveInt(_lastPostion);
|
storage.saveInt(_lastPostion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_queue.playlist.length == 1 || !_autoPlay) &&
|
if ((_queue.playlist.length == 1 || !_autoPlay) &&
|
||||||
_seekSliderValue == 1 &&
|
_seekSliderValue > 0.9 &&
|
||||||
_episode != null) {
|
_episode != null) {
|
||||||
_queue.delFromPlaylist(_episode);
|
_queue.delFromPlaylist(_episode);
|
||||||
_lastPostion = 0;
|
_lastPostion = 0;
|
||||||
storage.saveInt(_lastPostion);
|
storage.saveInt(_lastPostion);
|
||||||
PlayHistory history = PlayHistory(
|
final PlayHistory history = PlayHistory(
|
||||||
_episode.title,
|
_episode.title,
|
||||||
_episode.enclosureUrl,
|
_episode.enclosureUrl,
|
||||||
backgroundAudioPosition / 1000,
|
backgroundAudioPosition / 1000,
|
||||||
@ -325,6 +349,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
print('add to playlist when not rnnning');
|
print('add to playlist when not rnnning');
|
||||||
await _queue.addToPlayListAt(episode, index);
|
await _queue.addToPlayListAt(episode, index);
|
||||||
|
_queueUpdate = !_queueUpdate;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,17 +357,20 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
int index = _queue.playlist
|
int index = _queue.playlist
|
||||||
.indexWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
.indexWhere((item) => item.enclosureUrl == episode.enclosureUrl);
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
|
EpisodeBrief episodeNew =
|
||||||
|
await dbHelper.getRssItemWithUrl(episode.enclosureUrl);
|
||||||
await delFromPlaylist(episode);
|
await delFromPlaylist(episode);
|
||||||
await addToPlaylistAt(episode, index);
|
await addToPlaylistAt(episodeNew, index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delFromPlaylist(EpisodeBrief episode) async {
|
Future<int> delFromPlaylist(EpisodeBrief episode) async {
|
||||||
if (_playerRunning) {
|
if (_playerRunning) {
|
||||||
await AudioService.removeQueueItem(episode.toMediaItem());
|
await AudioService.removeQueueItem(episode.toMediaItem());
|
||||||
}
|
}
|
||||||
await _queue.delFromPlaylist(episode);
|
int index = await _queue.delFromPlaylist(episode);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToTop(EpisodeBrief episode) async {
|
moveToTop(EpisodeBrief episode) async {
|
||||||
@ -354,6 +382,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
_lastPostion = 0;
|
_lastPostion = 0;
|
||||||
storage.saveInt(_lastPostion);
|
storage.saveInt(_lastPostion);
|
||||||
}
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseAduio() async {
|
pauseAduio() async {
|
||||||
@ -361,22 +390,32 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resumeAudio() async {
|
resumeAudio() async {
|
||||||
AudioService.play();
|
if (_audioState != BasicPlaybackState.connecting &&
|
||||||
|
_audioState != BasicPlaybackState.none) AudioService.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardAudio(int s) {
|
forwardAudio(int s) {
|
||||||
int pos = _backgroundAudioPosition + s * 1000;
|
int pos = _backgroundAudioPosition + s * 1000;
|
||||||
AudioService.seekTo(pos);
|
AudioService.seekTo(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seekTo(int position) async{
|
||||||
|
if (_audioState != BasicPlaybackState.connecting &&
|
||||||
|
_audioState != BasicPlaybackState.none)
|
||||||
|
await AudioService.seekTo(position);
|
||||||
|
}
|
||||||
|
|
||||||
sliderSeek(double val) async {
|
sliderSeek(double val) async {
|
||||||
print(val.toString());
|
print(val.toString());
|
||||||
_noSlide = false;
|
if (_audioState != BasicPlaybackState.connecting &&
|
||||||
_seekSliderValue = val;
|
_audioState != BasicPlaybackState.none) {
|
||||||
notifyListeners();
|
_noSlide = false;
|
||||||
_currentPosition = (val * _backgroundAudioDuration).toInt();
|
_seekSliderValue = val;
|
||||||
await AudioService.seekTo(_currentPosition);
|
notifyListeners();
|
||||||
_noSlide = true;
|
_currentPosition = (val * _backgroundAudioDuration).toInt();
|
||||||
|
await AudioService.seekTo(_currentPosition);
|
||||||
|
_noSlide = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set sleep time
|
//Set sleep time
|
||||||
@ -459,6 +498,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
_handlePlaybackCompleted();
|
_handlePlaybackCompleted();
|
||||||
});
|
});
|
||||||
var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
|
var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
|
||||||
|
print('buffer position' + event.bufferedPosition.toString());
|
||||||
|
if (event.playbackError != null) {
|
||||||
|
_setState(state: BasicPlaybackState.error);
|
||||||
|
}
|
||||||
BasicPlaybackState state;
|
BasicPlaybackState state;
|
||||||
if (event.buffering) {
|
if (event.buffering) {
|
||||||
state = BasicPlaybackState.buffering;
|
state = BasicPlaybackState.buffering;
|
||||||
@ -514,7 +557,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
_skipState = BasicPlaybackState.skippingToNext;
|
_skipState = BasicPlaybackState.skippingToNext;
|
||||||
await _audioPlayer.setUrl(mediaItem.id);
|
await _audioPlayer.setUrl(mediaItem.id);
|
||||||
print(mediaItem.id);
|
print(mediaItem.id);
|
||||||
Duration duration = await _audioPlayer.durationFuture ?? 0;
|
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||||
AudioServiceBackground.setMediaItem(
|
AudioServiceBackground.setMediaItem(
|
||||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
mediaItem.copyWith(duration: duration.inMilliseconds));
|
||||||
_skipState = null;
|
_skipState = null;
|
||||||
@ -590,7 +633,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
|
|||||||
AudioServiceBackground.setQueue(_queue);
|
AudioServiceBackground.setQueue(_queue);
|
||||||
AudioServiceBackground.setMediaItem(mediaItem);
|
AudioServiceBackground.setMediaItem(mediaItem);
|
||||||
await _audioPlayer.setUrl(mediaItem.id);
|
await _audioPlayer.setUrl(mediaItem.id);
|
||||||
Duration duration = await _audioPlayer.durationFuture;
|
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
|
||||||
AudioServiceBackground.setMediaItem(
|
AudioServiceBackground.setMediaItem(
|
||||||
mediaItem.copyWith(duration: duration.inMilliseconds));
|
mediaItem.copyWith(duration: duration.inMilliseconds));
|
||||||
onPlay();
|
onPlay();
|
||||||
|
191
lib/class/download_state.dart
Normal file
191
lib/class/download_state.dart
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
|
|
||||||
|
import 'episodebrief.dart';
|
||||||
|
|
||||||
|
class EpisodeTask {
|
||||||
|
final String taskId;
|
||||||
|
int progress;
|
||||||
|
DownloadTaskStatus status;
|
||||||
|
final EpisodeBrief episode;
|
||||||
|
EpisodeTask(
|
||||||
|
this.episode,
|
||||||
|
this.taskId, {
|
||||||
|
this.progress = 0,
|
||||||
|
this.status = DownloadTaskStatus.undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void downloadCallback(String id, DownloadTaskStatus status, int progress) {
|
||||||
|
print('Homepage callback task in $id status ($status) $progress');
|
||||||
|
final SendPort send =
|
||||||
|
IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||||
|
send.send([id, status, progress]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DownloadState extends ChangeNotifier {
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
|
List<EpisodeTask> _episodeTasks = [];
|
||||||
|
List<EpisodeTask> get episodeTasks => _episodeTasks;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addListener(VoidCallback listener) async {
|
||||||
|
_loadTasks();
|
||||||
|
_bindBackgroundIsolate();
|
||||||
|
FlutterDownloader.registerCallback(downloadCallback);
|
||||||
|
super.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadTasks() async {
|
||||||
|
_episodeTasks = [];
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
|
var tasks = await FlutterDownloader.loadTasks();
|
||||||
|
if (tasks.length != 0)
|
||||||
|
await Future.forEach(tasks, (DownloadTask task) async {
|
||||||
|
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.url);
|
||||||
|
_episodeTasks.add(EpisodeTask(episode, task.taskId,
|
||||||
|
progress: task.progress, status: task.status));
|
||||||
|
});
|
||||||
|
print(_episodeTasks.length);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _bindBackgroundIsolate() {
|
||||||
|
ReceivePort _port = ReceivePort();
|
||||||
|
bool isSuccess = IsolateNameServer.registerPortWithName(
|
||||||
|
_port.sendPort, 'downloader_send_port');
|
||||||
|
if (!isSuccess) {
|
||||||
|
_unbindBackgroundIsolate();
|
||||||
|
_bindBackgroundIsolate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_port.listen((dynamic data) {
|
||||||
|
String id = data[0];
|
||||||
|
DownloadTaskStatus status = data[1];
|
||||||
|
int progress = data[2];
|
||||||
|
_episodeTasks.forEach((episodeTask) {
|
||||||
|
if (episodeTask.taskId == id) {
|
||||||
|
episodeTask.status = status;
|
||||||
|
episodeTask.progress = progress;
|
||||||
|
if (status == DownloadTaskStatus.complete) {
|
||||||
|
_saveMediaId(episodeTask).then((value) {
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _saveMediaId(EpisodeTask episodeTask) async {
|
||||||
|
episodeTask.status = DownloadTaskStatus.complete;
|
||||||
|
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _unbindBackgroundIsolate() {
|
||||||
|
IsolateNameServer.removePortNameMapping('downloader_send_port');
|
||||||
|
}
|
||||||
|
|
||||||
|
EpisodeTask episodeToTask(EpisodeBrief episode) {
|
||||||
|
return _episodeTasks
|
||||||
|
.firstWhere((task) => task.episode.enclosureUrl == episode.enclosureUrl,
|
||||||
|
orElse: () {
|
||||||
|
return EpisodeTask(
|
||||||
|
episode,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_unbindBackgroundIsolate();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future startTask(EpisodeBrief episode) async {
|
||||||
|
final dir = await getExternalStorageDirectory();
|
||||||
|
String localPath = path.join(dir.path, episode.feedTitle);
|
||||||
|
final saveDir = Directory(localPath);
|
||||||
|
bool hasExisted = await saveDir.exists();
|
||||||
|
if (!hasExisted) {
|
||||||
|
saveDir.create();
|
||||||
|
}
|
||||||
|
String taskId = await FlutterDownloader.enqueue(
|
||||||
|
url: episode.enclosureUrl,
|
||||||
|
savedDir: localPath,
|
||||||
|
showNotification: true,
|
||||||
|
openFileFromNotification: false,
|
||||||
|
);
|
||||||
|
_episodeTasks.add(EpisodeTask(episode, taskId));
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future pauseTask(EpisodeBrief episode) async {
|
||||||
|
EpisodeTask task = episodeToTask(episode);
|
||||||
|
await FlutterDownloader.pause(taskId: task.taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future resumeTask(EpisodeBrief episode) async {
|
||||||
|
EpisodeTask task = episodeToTask(episode);
|
||||||
|
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
|
||||||
|
int index = _episodeTasks.indexOf(task);
|
||||||
|
_removeTask(episode);
|
||||||
|
FlutterDownloader.remove(taskId: task.taskId);
|
||||||
|
var dbHelper = DBHelper();
|
||||||
|
_episodeTasks.insert(index, EpisodeTask(episode, newTaskId));
|
||||||
|
await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future retryTask(EpisodeBrief episode) async {
|
||||||
|
EpisodeTask task = episodeToTask(episode);
|
||||||
|
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
|
||||||
|
await FlutterDownloader.remove(taskId: task.taskId);
|
||||||
|
int index = _episodeTasks.indexOf(task);
|
||||||
|
_removeTask(episode);
|
||||||
|
var dbHelper = DBHelper();
|
||||||
|
_episodeTasks.insert(index, EpisodeTask(episode, newTaskId));
|
||||||
|
await dbHelper.saveDownloaded(newTaskId, episode.enclosureUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future removeTask(EpisodeBrief episode) async {
|
||||||
|
EpisodeTask task = episodeToTask(episode);
|
||||||
|
await FlutterDownloader.remove(
|
||||||
|
taskId: task.taskId, shouldDeleteContent: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future delTask(EpisodeBrief episode) async {
|
||||||
|
EpisodeTask task = episodeToTask(episode);
|
||||||
|
await FlutterDownloader.remove(
|
||||||
|
taskId: task.taskId, shouldDeleteContent: true);
|
||||||
|
await dbHelper.delDownloaded(episode.enclosureUrl);
|
||||||
|
_episodeTasks.forEach((episodeTask) {
|
||||||
|
if (episodeTask.taskId == task.taskId)
|
||||||
|
episodeTask.status = DownloadTaskStatus.undefined;
|
||||||
|
notifyListeners();
|
||||||
|
});
|
||||||
|
_removeTask(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeTask(EpisodeBrief episode) {
|
||||||
|
_episodeTasks.removeWhere(
|
||||||
|
(element) => element.episode.enclosureUrl == episode.enclosureUrl);
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
enum DownloadState { stop, load, donwload, complete, error }
|
|
||||||
|
|
||||||
class EpisodeDownload with ChangeNotifier {
|
|
||||||
String _title;
|
|
||||||
String get title => _title;
|
|
||||||
set title(String t) {
|
|
||||||
_title = t;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
DownloadState _downloadState = DownloadState.stop;
|
|
||||||
DownloadState get downloadState => _downloadState;
|
|
||||||
set downloadState(DownloadState state){
|
|
||||||
_downloadState = state;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,6 +15,7 @@ class EpisodeBrief {
|
|||||||
final int explicit;
|
final int explicit;
|
||||||
final String imagePath;
|
final String imagePath;
|
||||||
final String mediaId;
|
final String mediaId;
|
||||||
|
final int isNew;
|
||||||
EpisodeBrief(
|
EpisodeBrief(
|
||||||
this.title,
|
this.title,
|
||||||
this.enclosureUrl,
|
this.enclosureUrl,
|
||||||
@ -27,7 +28,8 @@ class EpisodeBrief {
|
|||||||
this.duration,
|
this.duration,
|
||||||
this.explicit,
|
this.explicit,
|
||||||
this.imagePath,
|
this.imagePath,
|
||||||
this.mediaId);
|
this.mediaId,
|
||||||
|
this.isNew);
|
||||||
|
|
||||||
String dateToString() {
|
String dateToString() {
|
||||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
|
DateTime date = DateTime.fromMillisecondsSinceEpoch(pubDate, isUtc: true);
|
||||||
|
@ -21,7 +21,6 @@ class GroupEntity {
|
|||||||
|
|
||||||
static GroupEntity fromJson(Map<String, Object> json) {
|
static GroupEntity fromJson(Map<String, Object> json) {
|
||||||
List<String> list = List.from(json['podcastList']);
|
List<String> list = List.from(json['podcastList']);
|
||||||
print(json['[podcastList']);
|
|
||||||
return GroupEntity(json['name'] as String, json['id'] as String,
|
return GroupEntity(json['name'] as String, json['id'] as String,
|
||||||
json['color'] as String, list);
|
json['color'] as String, list);
|
||||||
}
|
}
|
||||||
@ -58,6 +57,7 @@ class PodcastGroup {
|
|||||||
List<PodcastLocal> _orderedPodcasts;
|
List<PodcastLocal> _orderedPodcasts;
|
||||||
List<PodcastLocal> get ordereddPodcasts => _orderedPodcasts;
|
List<PodcastLocal> get ordereddPodcasts => _orderedPodcasts;
|
||||||
List<PodcastLocal> get podcasts => _podcasts;
|
List<PodcastLocal> get podcasts => _podcasts;
|
||||||
|
|
||||||
set setOrderedPodcasts(List<PodcastLocal> list) {
|
set setOrderedPodcasts(List<PodcastLocal> list) {
|
||||||
_orderedPodcasts = list;
|
_orderedPodcasts = list;
|
||||||
}
|
}
|
||||||
@ -88,18 +88,29 @@ class GroupList extends ChangeNotifier {
|
|||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
List<String> _orderChanged = [];
|
List<PodcastGroup> _orderChanged = [];
|
||||||
List<String> get orderChanged => _orderChanged;
|
List<PodcastGroup> get orderChanged => _orderChanged;
|
||||||
void addToOrderChanged(String name) {
|
|
||||||
_orderChanged.add(name);
|
void addToOrderChanged(PodcastGroup group) {
|
||||||
|
_orderChanged.add(group);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void drlFromOrderChanged(String name) {
|
void drlFromOrderChanged(String name) {
|
||||||
_orderChanged.remove(name);
|
_orderChanged.removeWhere((group) => group.name == name);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearOrderChanged() async {
|
||||||
|
if (_orderChanged.length > 0) {
|
||||||
|
await Future.forEach(_orderChanged, (PodcastGroup group) async {
|
||||||
|
await group.getPodcasts();
|
||||||
|
});
|
||||||
|
_orderChanged.clear();
|
||||||
|
// notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addListener(VoidCallback listener) {
|
void addListener(VoidCallback listener) {
|
||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
@ -162,6 +173,18 @@ class GroupList extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future updatePodcast(PodcastLocal podcastLocal) async {
|
||||||
|
List<int> counts = await dbHelper.getPodcastCounts(podcastLocal.id);
|
||||||
|
_groups.forEach((group) {
|
||||||
|
if (group.podcastList.contains(podcastLocal.id)) {
|
||||||
|
group.podcasts.firstWhere((podcast) => podcast.id == podcastLocal.id)
|
||||||
|
..upateCount = counts[0]
|
||||||
|
..episodeCount = counts[1];
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
List<PodcastGroup> getPodcastGroup(String id) {
|
List<PodcastGroup> getPodcastGroup(String id) {
|
||||||
List<PodcastGroup> result = [];
|
List<PodcastGroup> result = [];
|
||||||
_groups.forEach((group) {
|
_groups.forEach((group) {
|
||||||
|
@ -11,8 +11,8 @@ class PodcastLocal {
|
|||||||
final String link;
|
final String link;
|
||||||
|
|
||||||
final String description;
|
final String description;
|
||||||
final int upateCount;
|
int upateCount;
|
||||||
final int episodeCount;
|
int episodeCount;
|
||||||
PodcastLocal(
|
PodcastLocal(
|
||||||
this.title,
|
this.title,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
|
@ -10,24 +10,23 @@ void callbackDispatcher() {
|
|||||||
Workmanager.executeTask((task, inputData) async {
|
Workmanager.executeTask((task, inputData) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocalAll();
|
||||||
int i = 0;
|
|
||||||
await Future.forEach(podcastList, (podcastLocal) async {
|
await Future.forEach(podcastList, (podcastLocal) async {
|
||||||
i += await dbHelper.updatePodcastRss(podcastLocal);
|
await dbHelper.updatePodcastRss(podcastLocal);
|
||||||
print('Refresh ' + podcastLocal.title);
|
print('Refresh ' + podcastLocal.title);
|
||||||
});
|
});
|
||||||
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
KeyValueStorage refreshstorage = KeyValueStorage('refreshdate');
|
||||||
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch);
|
||||||
KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
|
||||||
await refreshcountstorage.saveInt(i);
|
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingState extends ChangeNotifier {
|
class SettingState extends ChangeNotifier {
|
||||||
KeyValueStorage themestorage = KeyValueStorage('themes');
|
KeyValueStorage themeStorage = KeyValueStorage('themes');
|
||||||
KeyValueStorage accentstorage = KeyValueStorage('accents');
|
KeyValueStorage accentStorage = KeyValueStorage('accents');
|
||||||
KeyValueStorage autoupdatestorage = KeyValueStorage('autoupdate');
|
KeyValueStorage autoupdateStorage = KeyValueStorage('autoupdate');
|
||||||
KeyValueStorage intervalstorage = KeyValueStorage('updateInterval');
|
KeyValueStorage intervalStorage = KeyValueStorage('updateInterval');
|
||||||
|
KeyValueStorage downloadUsingDataStorage =
|
||||||
|
KeyValueStorage('downloadUsingData');
|
||||||
|
|
||||||
Future initData() async {
|
Future initData() async {
|
||||||
await _getTheme();
|
await _getTheme();
|
||||||
@ -49,7 +48,7 @@ class SettingState extends ChangeNotifier {
|
|||||||
_saveUpdateInterval();
|
_saveUpdateInterval();
|
||||||
Workmanager.initialize(
|
Workmanager.initialize(
|
||||||
callbackDispatcher,
|
callbackDispatcher,
|
||||||
isInDebugMode: true,
|
isInDebugMode: false,
|
||||||
);
|
);
|
||||||
Workmanager.registerPeriodicTask("1", "update_podcasts",
|
Workmanager.registerPeriodicTask("1", "update_podcasts",
|
||||||
frequency: Duration(hours: hour),
|
frequency: Duration(hours: hour),
|
||||||
@ -60,8 +59,8 @@ class SettingState extends ChangeNotifier {
|
|||||||
print('work manager init done + ');
|
print('work manager init done + ');
|
||||||
}
|
}
|
||||||
|
|
||||||
void cancelWork() {
|
Future cancelWork() async{
|
||||||
Workmanager.cancelAll();
|
await Workmanager.cancelAll();
|
||||||
print('work job cancelled');
|
print('work job cancelled');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,58 +85,74 @@ class SettingState extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _downloadUsingData;
|
||||||
|
bool get downloadUsingData => _downloadUsingData;
|
||||||
|
set downloadUsingData(bool boo) {
|
||||||
|
_downloadUsingData = boo;
|
||||||
|
_saveDownloadUsingData();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addListener(VoidCallback listener) {
|
void addListener(VoidCallback listener) {
|
||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
_getTheme();
|
_getTheme();
|
||||||
_getAccentSetColor();
|
_getAccentSetColor();
|
||||||
_getAutoUpdate();
|
_getAutoUpdate();
|
||||||
|
_getDownloadUsingData();
|
||||||
_getUpdateInterval().then((value) {
|
_getUpdateInterval().then((value) {
|
||||||
if (_initUpdateTag == 0) setWorkManager(24);
|
if (_initUpdateTag == 0) setWorkManager(24);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getTheme() async {
|
Future _getTheme() async {
|
||||||
int mode = await themestorage.getInt();
|
int mode = await themeStorage.getInt();
|
||||||
_theme = ThemeMode.values[mode];
|
_theme = ThemeMode.values[mode];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _saveTheme() async {
|
Future _saveTheme() async {
|
||||||
await themestorage.saveInt(_theme.index);
|
await themeStorage.saveInt(_theme.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getAccentSetColor() async {
|
Future _getAccentSetColor() async {
|
||||||
String colorString = await accentstorage.getString();
|
String colorString = await accentStorage.getString();
|
||||||
print(colorString);
|
|
||||||
if (colorString.isNotEmpty) {
|
if (colorString.isNotEmpty) {
|
||||||
int color = int.parse('FF' + colorString.toUpperCase(), radix: 16);
|
int color = int.parse('FF' + colorString.toUpperCase(), radix: 16);
|
||||||
_accentSetColor = Color(color).withOpacity(1.0);
|
_accentSetColor = Color(color).withOpacity(1.0);
|
||||||
print(_accentSetColor.toString());
|
|
||||||
} else {
|
} else {
|
||||||
_accentSetColor = Colors.blue[400];
|
_accentSetColor = Colors.blue[400];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _saveAccentSetColor() async {
|
Future _saveAccentSetColor() async {
|
||||||
await accentstorage
|
await accentStorage
|
||||||
.saveString(_accentSetColor.toString().substring(10, 16));
|
.saveString(_accentSetColor.toString().substring(10, 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getAutoUpdate() async {
|
Future _getAutoUpdate() async {
|
||||||
int i = await autoupdatestorage.getInt();
|
int i = await autoupdateStorage.getInt();
|
||||||
_autoUpdate = i == 0 ? true : false;
|
_autoUpdate = i == 0 ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _saveAutoUpdate() async {
|
Future _saveAutoUpdate() async {
|
||||||
await autoupdatestorage.saveInt(_autoUpdate ? 0 : 1);
|
await autoupdateStorage.saveInt(_autoUpdate ? 0 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _getUpdateInterval() async {
|
Future _getUpdateInterval() async {
|
||||||
_initUpdateTag = await intervalstorage.getInt();
|
_initUpdateTag = await intervalStorage.getInt();
|
||||||
_updateInterval = _initUpdateTag;
|
_updateInterval = _initUpdateTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _saveUpdateInterval() async {
|
Future _saveUpdateInterval() async {
|
||||||
await intervalstorage.saveInt(_updateInterval);
|
await intervalStorage.saveInt(_updateInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _getDownloadUsingData() async {
|
||||||
|
int i = await downloadUsingDataStorage.getInt();
|
||||||
|
_downloadUsingData = i == 0 ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _saveDownloadUsingData() async {
|
||||||
|
await downloadUsingDataStorage.saveInt(_downloadUsingData ? 0 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
import 'package:tsacdop/class/download_state.dart';
|
||||||
import 'package:tsacdop/home/audioplayer.dart';
|
import 'package:tsacdop/home/audioplayer.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
@ -382,7 +383,7 @@ class _MenuBarState extends State<MenuBar> {
|
|||||||
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
DownloadButton(episodeBrief: widget.episodeItem),
|
DownloadButton(episode: widget.episodeItem),
|
||||||
Selector<AudioPlayerNotifier, List<String>>(
|
Selector<AudioPlayerNotifier, List<String>>(
|
||||||
selector: (_, audio) =>
|
selector: (_, audio) =>
|
||||||
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
|
audio.queue.playlist.map((e) => e.enclosureUrl).toList(),
|
||||||
|
@ -1,193 +1,95 @@
|
|||||||
import 'dart:isolate';
|
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:connectivity/connectivity.dart';
|
||||||
|
import 'package:tsacdop/class/download_state.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
import 'package:tsacdop/class/settingstate.dart';
|
||||||
|
|
||||||
class DownloadButton extends StatefulWidget {
|
class DownloadButton extends StatefulWidget {
|
||||||
final EpisodeBrief episodeBrief;
|
final EpisodeBrief episode;
|
||||||
DownloadButton({this.episodeBrief, Key key}) : super(key: key);
|
DownloadButton({this.episode, Key key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
_DownloadButtonState createState() => _DownloadButtonState();
|
_DownloadButtonState createState() => _DownloadButtonState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DownloadButtonState extends State<DownloadButton> {
|
class _DownloadButtonState extends State<DownloadButton> {
|
||||||
_TaskInfo _task;
|
|
||||||
bool _isLoading;
|
|
||||||
bool _permissionReady;
|
bool _permissionReady;
|
||||||
String _localPath;
|
bool _usingData;
|
||||||
ReceivePort _port = ReceivePort();
|
StreamSubscription _connectivity;
|
||||||
|
EpisodeTask _task;
|
||||||
Future<String> _getPath() async {
|
|
||||||
final dir = await getExternalStorageDirectory();
|
|
||||||
return dir.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
_bindBackgroundIsolate();
|
|
||||||
|
|
||||||
FlutterDownloader.registerCallback(downloadCallback);
|
|
||||||
|
|
||||||
_isLoading = true;
|
|
||||||
_permissionReady = false;
|
_permissionReady = false;
|
||||||
|
_connectivity = Connectivity().onConnectivityChanged.listen((result) {
|
||||||
_prepare();
|
_usingData = result == ConnectivityResult.mobile;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_unbindBackgroundIsolate();
|
_connectivity.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _bindBackgroundIsolate() {
|
void _requestDownload(EpisodeBrief episode, bool downloadUsingData) async {
|
||||||
bool isSuccess = IsolateNameServer.registerPortWithName(
|
|
||||||
_port.sendPort, 'downloader_send_port');
|
|
||||||
if (!isSuccess) {
|
|
||||||
_unbindBackgroundIsolate();
|
|
||||||
_bindBackgroundIsolate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_port.listen((dynamic data) {
|
|
||||||
print('UI isolate callback: $data');
|
|
||||||
String id = data[0];
|
|
||||||
DownloadTaskStatus status = data[1];
|
|
||||||
int progress = data[2];
|
|
||||||
if (_task.taskId == id) {
|
|
||||||
print(_task.progress);
|
|
||||||
setState(() {
|
|
||||||
_task.status = status;
|
|
||||||
_task.progress = progress;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unbindBackgroundIsolate() {
|
|
||||||
IsolateNameServer.removePortNameMapping('downloader_send_port');
|
|
||||||
}
|
|
||||||
|
|
||||||
static void downloadCallback(
|
|
||||||
String id, DownloadTaskStatus status, int progress) {
|
|
||||||
print('Background callback task in $id status ($status) $progress');
|
|
||||||
final SendPort send =
|
|
||||||
IsolateNameServer.lookupPortByName('downloader_send_port');
|
|
||||||
send.send([id, status, progress]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _requestDownload(_TaskInfo task) async {
|
|
||||||
_permissionReady = await _checkPermmison();
|
_permissionReady = await _checkPermmison();
|
||||||
if (_permissionReady)
|
bool _dataConfirm = true;
|
||||||
task.taskId = await FlutterDownloader.enqueue(
|
if (_permissionReady) {
|
||||||
url: task.link,
|
if (downloadUsingData && _usingData) {
|
||||||
savedDir: _localPath,
|
_dataConfirm = await _useDataConfirem();
|
||||||
showNotification: true,
|
}
|
||||||
openFileFromNotification: false,
|
if (_dataConfirm) {
|
||||||
);
|
Provider.of<DownloadState>(context, listen: false).startTask(episode);
|
||||||
var dbHelper = DBHelper();
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Downloading',
|
||||||
await dbHelper.saveDownloaded(task.link, task.taskId);
|
gravity: ToastGravity.BOTTOM,
|
||||||
Fluttertoast.showToast(
|
);
|
||||||
msg: 'Downloading',
|
}
|
||||||
gravity: ToastGravity.BOTTOM,
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteDownload(_TaskInfo task) async {
|
void _deleteDownload(EpisodeBrief episode) async {
|
||||||
await FlutterDownloader.remove(
|
Provider.of<DownloadState>(context, listen: false).delTask(episode);
|
||||||
taskId: task.taskId, shouldDeleteContent: true);
|
|
||||||
var dbHelper = DBHelper();
|
|
||||||
await dbHelper.delDownloaded(task.link);
|
|
||||||
await _prepare();
|
|
||||||
setState(() {});
|
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Download removed',
|
msg: 'Download removed',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pauseDownload(_TaskInfo task) async {
|
void _pauseDownload(EpisodeBrief episode) async {
|
||||||
await FlutterDownloader.pause(taskId: task.taskId);
|
Provider.of<DownloadState>(context, listen: false).pauseTask(episode);
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Download paused',
|
msg: 'Download paused',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resumeDownload(_TaskInfo task) async {
|
void _resumeDownload(EpisodeBrief episode) async {
|
||||||
String newTaskId = await FlutterDownloader.resume(taskId: task.taskId);
|
Provider.of<DownloadState>(context, listen: false).resumeTask(episode);
|
||||||
task.taskId = newTaskId;
|
|
||||||
var dbHelper = DBHelper();
|
|
||||||
await dbHelper.saveDownloaded(task.taskId, task.link);
|
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Download resumed',
|
msg: 'Download resumed',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _retryDownload(_TaskInfo task) async {
|
void _retryDownload(EpisodeBrief episode) async {
|
||||||
String newTaskId = await FlutterDownloader.retry(taskId: task.taskId);
|
Provider.of<DownloadState>(context, listen: false).retryTask(episode);
|
||||||
task.taskId = newTaskId;
|
|
||||||
var dbHelper = DBHelper();
|
|
||||||
await dbHelper.saveDownloaded(task.taskId, task.link);
|
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Download again',
|
msg: 'Download again',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<EpisodeBrief> _saveMediaId(_TaskInfo task) async {
|
|
||||||
final completeTask = await FlutterDownloader.loadTasksWithRawQuery(
|
|
||||||
query: "SELECT * FROM task WHERE url = '${task.link}'");
|
|
||||||
String filePath = 'file://' +
|
|
||||||
path.join(completeTask.first.savedDir, completeTask.first.filename);
|
|
||||||
var dbHelper = DBHelper();
|
|
||||||
await dbHelper.saveMediaId(task.link, filePath);
|
|
||||||
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(task.link);
|
|
||||||
return episode;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Null> _prepare() async {
|
|
||||||
final tasks = await FlutterDownloader.loadTasks();
|
|
||||||
|
|
||||||
_task = _TaskInfo(
|
|
||||||
name: widget.episodeBrief.title,
|
|
||||||
link: widget.episodeBrief.enclosureUrl,
|
|
||||||
);
|
|
||||||
tasks?.forEach((task) {
|
|
||||||
if (_task.link == task.url) {
|
|
||||||
_task.taskId = task.taskId;
|
|
||||||
_task.status = task.status;
|
|
||||||
_task.progress = task.progress;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_localPath = path.join((await _getPath()), widget.episodeBrief.feedTitle);
|
|
||||||
print(_localPath);
|
|
||||||
final saveDir = Directory(_localPath);
|
|
||||||
bool hasExisted = await saveDir.exists();
|
|
||||||
if (!hasExisted) {
|
|
||||||
saveDir.create();
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _checkPermmison() async {
|
Future<bool> _checkPermmison() async {
|
||||||
PermissionStatus permission = await PermissionHandler()
|
PermissionStatus permission = await PermissionHandler()
|
||||||
.checkPermissionStatus(PermissionGroup.storage);
|
.checkPermissionStatus(PermissionGroup.storage);
|
||||||
@ -205,6 +107,60 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> _useDataConfirem() async {
|
||||||
|
bool ifUseData = false;
|
||||||
|
await showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
|
barrierColor: Colors.black54,
|
||||||
|
transitionDuration: const Duration(milliseconds: 200),
|
||||||
|
pageBuilder: (BuildContext context, Animation animaiton,
|
||||||
|
Animation secondaryAnimation) =>
|
||||||
|
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
|
value: SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
systemNavigationBarColor:
|
||||||
|
Theme.of(context).brightness == Brightness.light
|
||||||
|
? Color.fromRGBO(113, 113, 113, 1)
|
||||||
|
: Color.fromRGBO(15, 15, 15, 1),
|
||||||
|
),
|
||||||
|
child: AlertDialog(
|
||||||
|
elevation: 1,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(10.0))),
|
||||||
|
titlePadding:
|
||||||
|
EdgeInsets.only(top: 20, left: 20, right: 100, bottom: 20),
|
||||||
|
title: Text('Cellular data warn'),
|
||||||
|
content:
|
||||||
|
Text('Are you sure you want to use cellular data to download?'),
|
||||||
|
actions: <Widget>[
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'CANCEL',
|
||||||
|
style: TextStyle(color: Colors.grey[600]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () {
|
||||||
|
ifUseData = true;
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
'CONFIRM',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return ifUseData;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buttonOnMenu(Widget widget, Function() onTap) => Material(
|
Widget _buttonOnMenu(Widget widget, Function() onTap) => Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@ -218,112 +174,111 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return _isLoading
|
return Consumer<DownloadState>(builder: (_, downloader, __) {
|
||||||
? Center()
|
EpisodeTask _task = Provider.of<DownloadState>(context, listen: false)
|
||||||
: Row(
|
.episodeToTask(widget.episode);
|
||||||
children: <Widget>[
|
return Row(
|
||||||
_downloadButton(_task),
|
children: <Widget>[
|
||||||
AnimatedContainer(
|
_downloadButton(_task, context),
|
||||||
duration: Duration(seconds: 1),
|
AnimatedContainer(
|
||||||
decoration: BoxDecoration(
|
duration: Duration(seconds: 1),
|
||||||
color: Theme.of(context).accentColor,
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
color: Theme.of(context).accentColor,
|
||||||
height: 20.0,
|
borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||||
width:
|
height: 20.0,
|
||||||
(_task.status == DownloadTaskStatus.running) ? 50.0 : 0,
|
width: (_task.status == DownloadTaskStatus.running) ? 50.0 : 0,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Text('${_task.progress}%',
|
child: Text('${_task.progress}%',
|
||||||
style: TextStyle(color: Colors.white)),
|
style: TextStyle(color: Colors.white)),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _downloadButton(_TaskInfo task) {
|
Widget _downloadButton(EpisodeTask task, BuildContext context) {
|
||||||
if (task.status == DownloadTaskStatus.undefined) {
|
switch (task.status.value) {
|
||||||
return _buttonOnMenu(
|
case 0:
|
||||||
Icon(
|
return Selector<SettingState, bool>(
|
||||||
Icons.arrow_downward,
|
selector: (_, settings) => settings.downloadUsingData,
|
||||||
color: Colors.grey[700],
|
builder: (_, data, __) => _buttonOnMenu(
|
||||||
),
|
Icon(
|
||||||
() => _requestDownload(task));
|
Icons.arrow_downward,
|
||||||
} else if (task.status == DownloadTaskStatus.running) {
|
color: Colors.grey[700],
|
||||||
return Material(
|
),
|
||||||
color: Colors.transparent,
|
() => _requestDownload(task.episode, data)),
|
||||||
child: InkWell(
|
);
|
||||||
onTap: () {
|
break;
|
||||||
if (task.progress > 0) _pauseDownload(task);
|
case 2:
|
||||||
},
|
return Material(
|
||||||
child: Container(
|
color: Colors.transparent,
|
||||||
height: 50.0,
|
child: InkWell(
|
||||||
alignment: Alignment.center,
|
onTap: () {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 18.0),
|
if (task.progress > 0) _pauseDownload(task.episode);
|
||||||
child: SizedBox(
|
},
|
||||||
height: 18,
|
child: Container(
|
||||||
width: 18,
|
height: 50.0,
|
||||||
child: CircularProgressIndicator(
|
alignment: Alignment.center,
|
||||||
backgroundColor: Colors.grey[500],
|
padding: EdgeInsets.symmetric(horizontal: 18.0),
|
||||||
strokeWidth: 2,
|
child: SizedBox(
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(
|
height: 18,
|
||||||
Theme.of(context).accentColor),
|
width: 18,
|
||||||
value: task.progress / 100,
|
child: CircularProgressIndicator(
|
||||||
|
backgroundColor: Colors.grey[500],
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Theme.of(context).accentColor),
|
||||||
|
value: task.progress / 100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
break;
|
||||||
} else if (task.status == DownloadTaskStatus.paused) {
|
case 6:
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_resumeDownload(task);
|
_resumeDownload(task.episode);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 18),
|
padding: EdgeInsets.symmetric(horizontal: 18),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 18,
|
height: 18,
|
||||||
width: 18,
|
width: 18,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
backgroundColor: Colors.grey[500],
|
backgroundColor: Colors.grey[500],
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
|
||||||
value: task.progress / 100,
|
value: task.progress / 100,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
break;
|
||||||
} else if (task.status == DownloadTaskStatus.complete) {
|
case 3:
|
||||||
if(!widget.episodeBrief.mediaId.contains('file://'))
|
Provider.of<AudioPlayerNotifier>(context, listen: false)
|
||||||
{_saveMediaId(task).then((episode) =>
|
.updateMediaItem(task.episode);
|
||||||
Provider.of<AudioPlayerNotifier>(context, listen: false)
|
return _buttonOnMenu(
|
||||||
.updateMediaItem(episode));}
|
Icon(
|
||||||
return _buttonOnMenu(
|
Icons.done_all,
|
||||||
Icon(
|
color: Theme.of(context).accentColor,
|
||||||
Icons.done_all,
|
), () {
|
||||||
color: Theme.of(context).accentColor,
|
_deleteDownload(task.episode);
|
||||||
),
|
});
|
||||||
() => _deleteDownload(task));
|
break;
|
||||||
} else if (task.status == DownloadTaskStatus.failed) {
|
case 4:
|
||||||
return _buttonOnMenu(
|
return _buttonOnMenu(Icon(Icons.refresh, color: Colors.red),
|
||||||
Icon(Icons.refresh, color: Colors.red), () => _retryDownload(task));
|
() => _retryDownload(task.episode));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Center();
|
||||||
}
|
}
|
||||||
return Center();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TaskInfo {
|
|
||||||
final String name;
|
|
||||||
final String link;
|
|
||||||
|
|
||||||
String taskId;
|
|
||||||
int progress = 0;
|
|
||||||
DownloadTaskStatus status = DownloadTaskStatus.undefined;
|
|
||||||
|
|
||||||
_TaskInfo({this.name, this.link});
|
|
||||||
}
|
|
||||||
|
@ -80,7 +80,7 @@ class AboutApp extends StatelessWidget {
|
|||||||
padding: EdgeInsets.symmetric(horizontal: 50),
|
padding: EdgeInsets.symmetric(horizontal: 50),
|
||||||
height: 50,
|
height: 50,
|
||||||
child: Text(
|
child: Text(
|
||||||
'Tsacdop is a podcasts client developed in flutter, a simple, beautiful, and easy-use application.',
|
'Tsacdop is a podcast player developed in flutter, a simple, beautiful, and easy-use application.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -110,12 +110,12 @@ class AboutApp extends StatelessWidget {
|
|||||||
_listItem(context, 'GitHub', LineIcons.github,
|
_listItem(context, 'GitHub', LineIcons.github,
|
||||||
'https://github.com/stonaga/'),
|
'https://github.com/stonaga/'),
|
||||||
_listItem(context, 'Twitter', LineIcons.twitter,
|
_listItem(context, 'Twitter', LineIcons.twitter,
|
||||||
'https://twitter.com'),
|
'https://twitter.com/shimenmen'),
|
||||||
_listItem(
|
_listItem(
|
||||||
context,
|
context,
|
||||||
'Stone Gate',
|
'Stone Gate',
|
||||||
LineIcons.hat_cowboy_solid,
|
LineIcons.medium,
|
||||||
'mailto:<xijieyin@gmail.com>?subject=Tsacdop Feedback'),
|
'https://medium.com/@stonegate'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -91,7 +91,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
class _MyHomePageDelegate extends SearchDelegate<int> {
|
class _MyHomePageDelegate extends SearchDelegate<int> {
|
||||||
static Future<List> getList(String searchText) async {
|
static Future<List> getList(String searchText) async {
|
||||||
String apiKey = environment['apiKey'];
|
String apiKey = environment['apiKey'];
|
||||||
print(apiKey);
|
|
||||||
String url =
|
String url =
|
||||||
"https://listennotes.p.mashape.com/api/v1/search?only_in=title%2Cdescription&q=" +
|
"https://listennotes.p.mashape.com/api/v1/search?only_in=title%2Cdescription&q=" +
|
||||||
searchText +
|
searchText +
|
||||||
@ -312,7 +311,7 @@ class _SearchResultState extends State<SearchResult> {
|
|||||||
importOmpl.importState = ImportState.parse;
|
importOmpl.importState = ImportState.parse;
|
||||||
|
|
||||||
await dbHelper.savePodcastRss(_p, _uuid);
|
await dbHelper.savePodcastRss(_p, _uuid);
|
||||||
|
groupList.updatePodcast(podcastLocal);
|
||||||
importOmpl.importState = ImportState.complete;
|
importOmpl.importState = ImportState.complete;
|
||||||
} else {
|
} else {
|
||||||
importOmpl.importState = ImportState.error;
|
importOmpl.importState = ImportState.error;
|
||||||
|
@ -173,7 +173,7 @@ class _PopupMenuState extends State<PopupMenu> {
|
|||||||
importOmpl.importState = ImportState.parse;
|
importOmpl.importState = ImportState.parse;
|
||||||
|
|
||||||
await dbHelper.savePodcastRss(_p, _uuid);
|
await dbHelper.savePodcastRss(_p, _uuid);
|
||||||
|
groupList.updatePodcast(podcastLocal);
|
||||||
importOmpl.importState = ImportState.complete;
|
importOmpl.importState = ImportState.complete;
|
||||||
} else {
|
} else {
|
||||||
importOmpl.importState = ImportState.error;
|
importOmpl.importState = ImportState.error;
|
||||||
|
@ -5,8 +5,11 @@ import 'dart:math' as math;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:marquee/marquee.dart';
|
import 'package:marquee/marquee.dart';
|
||||||
|
import 'package:tsacdop/home/playlist.dart';
|
||||||
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:line_icons/line_icons.dart';
|
||||||
|
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
@ -15,6 +18,7 @@ import 'package:tsacdop/home/audiopanel.dart';
|
|||||||
import 'package:tsacdop/util/pageroute.dart';
|
import 'package:tsacdop/util/pageroute.dart';
|
||||||
import 'package:tsacdop/util/colorize.dart';
|
import 'package:tsacdop/util/colorize.dart';
|
||||||
import 'package:tsacdop/util/day_night_switch.dart';
|
import 'package:tsacdop/util/day_night_switch.dart';
|
||||||
|
import 'package:tsacdop/util/context_extension.dart';
|
||||||
|
|
||||||
class MyRectangularTrackShape extends RectangularSliderTrackShape {
|
class MyRectangularTrackShape extends RectangularSliderTrackShape {
|
||||||
Rect getPreferredRect({
|
Rect getPreferredRect({
|
||||||
@ -143,18 +147,13 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
|
|
||||||
List minsToSelect = [1, 5, 10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
|
List minsToSelect = [1, 5, 10, 15, 20, 25, 30, 45, 60, 70, 80, 90, 99];
|
||||||
int _minSelected;
|
int _minSelected;
|
||||||
|
final GlobalKey<AnimatedListState> _miniPlaylistKey = GlobalKey();
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_minSelected = 30;
|
_minSelected = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(Widget oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _sleppMode(BuildContext context) {
|
Widget _sleppMode(BuildContext context) {
|
||||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
return Selector<AudioPlayerNotifier, Tuple3<bool, int, double>>(
|
return Selector<AudioPlayerNotifier, Tuple3<bool, int, double>>(
|
||||||
@ -571,9 +570,14 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
Container(
|
Container(
|
||||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||||
width: 200,
|
width: 200,
|
||||||
child: Text(data.item1.feedTitle, maxLines: 1, overflow: TextOverflow.fade,),
|
child: Text(
|
||||||
|
data.item1.feedTitle,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
|
LastPosition(),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => Navigator.push(
|
onPressed: () => Navigator.push(
|
||||||
context,
|
context,
|
||||||
@ -596,138 +600,252 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
Widget _playlist(BuildContext context) {
|
Widget _playlist(BuildContext context) {
|
||||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: AnimatedContainer(
|
height: 300,
|
||||||
duration: Duration(milliseconds: 400),
|
width: MediaQuery.of(context).size.width,
|
||||||
height: 300,
|
decoration: BoxDecoration(
|
||||||
width: MediaQuery.of(context).size.width,
|
color: Theme.of(context).primaryColor,
|
||||||
alignment: Alignment.center,
|
),
|
||||||
// margin: EdgeInsets.all(20),
|
child: Column(
|
||||||
//padding: EdgeInsets.only(bottom: 10.0),
|
children: <Widget>[
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
// borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
color: Theme.of(context).primaryColor,
|
height: 60.0,
|
||||||
),
|
// color: context.primaryColorDark,
|
||||||
child: Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
|
alignment: Alignment.centerLeft,
|
||||||
selector: (_, audio) => audio.queue.playlist,
|
child: Row(
|
||||||
builder: (_, playlist, __) {
|
children: <Widget>[
|
||||||
return ListView.builder(
|
Container(
|
||||||
shrinkWrap: true,
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
scrollDirection: Axis.vertical,
|
height: 20.0,
|
||||||
itemCount: playlist.length,
|
// color: context.primaryColorDark,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
alignment: Alignment.centerLeft,
|
||||||
print(playlist.length);
|
child: Text(
|
||||||
if (index == 0) {
|
'Playlist',
|
||||||
return Container(
|
style: TextStyle(
|
||||||
height: 60,
|
color: Theme.of(context).accentColor,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Container(
|
||||||
|
height: 60,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Row(
|
height: 30,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
width: 60,
|
||||||
mainAxisSize: MainAxisSize.min,
|
decoration: BoxDecoration(
|
||||||
children: <Widget>[
|
color: Theme.of(context).primaryColor,
|
||||||
Text(
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
'Playlist',
|
border: Border.all(
|
||||||
style: TextStyle(
|
color:
|
||||||
color: Theme.of(context).accentColor,
|
Theme.of(context).brightness == Brightness.dark
|
||||||
fontWeight: FontWeight.bold,
|
? Colors.black12
|
||||||
fontSize: 16),
|
: Colors.white10,
|
||||||
),
|
width: 1),
|
||||||
Spacer(),
|
boxShadow:
|
||||||
Container(
|
Theme.of(context).brightness == Brightness.dark
|
||||||
alignment: Alignment.center,
|
? _customShadowNight
|
||||||
child: Container(
|
: _customShadow),
|
||||||
alignment: Alignment.center,
|
child: Material(
|
||||||
height: 40,
|
color: Colors.transparent,
|
||||||
width: 80,
|
child: InkWell(
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
color: Theme.of(context).primaryColor,
|
onTap: () {
|
||||||
borderRadius:
|
audio.playNext();
|
||||||
BorderRadius.all(Radius.circular(20)),
|
_miniPlaylistKey.currentState.removeItem(
|
||||||
border: Border.all(
|
0, (context, animation) => Container());
|
||||||
color: Theme.of(context).brightness ==
|
_miniPlaylistKey.currentState.removeItem(
|
||||||
Brightness.dark
|
1, (context, animation) => Container());
|
||||||
? Colors.black12
|
_miniPlaylistKey.currentState.insertItem(0);
|
||||||
: Colors.white10,
|
},
|
||||||
width: 1),
|
child: SizedBox(
|
||||||
boxShadow: Theme.of(context).brightness ==
|
height: 30,
|
||||||
Brightness.dark
|
width: 60,
|
||||||
? _customShadowNight
|
child: Icon(
|
||||||
: _customShadow),
|
Icons.skip_next,
|
||||||
child: Material(
|
size: 30,
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(Radius.circular(20)),
|
|
||||||
onTap: () => audio.playNext(),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 40,
|
|
||||||
width: 80,
|
|
||||||
child: Icon(
|
|
||||||
Icons.skip_next,
|
|
||||||
size: 30,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
audio.episodeLoad(playlist[index]);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 60,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(10.0),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(Radius.circular(15.0)),
|
|
||||||
child: Container(
|
|
||||||
height: 30.0,
|
|
||||||
width: 30.0,
|
|
||||||
child: Image.file(File(
|
|
||||||
"${playlist[index].imagePath}"))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
playlist[index].title,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(height: 2),
|
),
|
||||||
],
|
),
|
||||||
);
|
),
|
||||||
}
|
Container(
|
||||||
|
margin: EdgeInsets.only(left: 20),
|
||||||
|
width: 30.0,
|
||||||
|
height: 30.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: (Theme.of(context).brightness == Brightness.dark)
|
||||||
|
? _customShadowNight
|
||||||
|
: _customShadow,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
SlideLeftRoute(page: PlaylistPage()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: math.pi,
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.database_solid,
|
||||||
|
size: 20.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Selector<AudioPlayerNotifier, List<EpisodeBrief>>(
|
||||||
|
selector: (_, audio) => audio.queue.playlist,
|
||||||
|
builder: (_, playlist, __) {
|
||||||
|
return AnimatedList(
|
||||||
|
key: _miniPlaylistKey,
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
initialItemCount: playlist.length,
|
||||||
|
itemBuilder: (context, index, animation) => ScaleTransition(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
scale: animation,
|
||||||
|
child: index == 0
|
||||||
|
? Center()
|
||||||
|
: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
audio.episodeLoad(playlist[index]);
|
||||||
|
_miniPlaylistKey.currentState
|
||||||
|
.removeItem(
|
||||||
|
index,
|
||||||
|
(context, animation) =>
|
||||||
|
Center());
|
||||||
|
_miniPlaylistKey.currentState
|
||||||
|
.insertItem(0);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 60,
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(
|
||||||
|
Radius.circular(
|
||||||
|
15.0)),
|
||||||
|
child: Container(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: Image.file(File(
|
||||||
|
"${playlist[index].imagePath}"))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
alignment:
|
||||||
|
Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
playlist[index].title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin:
|
||||||
|
EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
width: 30.0,
|
||||||
|
height: 30.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow:
|
||||||
|
(Theme.of(context).brightness ==
|
||||||
|
Brightness.dark)
|
||||||
|
? _customShadowNight
|
||||||
|
: _customShadow,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(15.0)),
|
||||||
|
onTap: () async {
|
||||||
|
await audio
|
||||||
|
.moveToTop(playlist[index]);
|
||||||
|
_miniPlaylistKey.currentState
|
||||||
|
.removeItem(
|
||||||
|
index,
|
||||||
|
(context, animation) => Center(),
|
||||||
|
duration:
|
||||||
|
Duration(milliseconds: 500),
|
||||||
|
);
|
||||||
|
_miniPlaylistKey.currentState
|
||||||
|
.insertItem(1,
|
||||||
|
duration: Duration(
|
||||||
|
milliseconds: 200));
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: math.pi,
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.download_solid,
|
||||||
|
size: 20.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Divider(height: 2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -765,8 +883,8 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
tabs: <Widget>[
|
tabs: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
// child: Text('p'),
|
// child: Text('p'),
|
||||||
height: 10.0,
|
height: 8.0,
|
||||||
width: 10.0,
|
width: 8.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@ -775,8 +893,8 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
width: 2.0)),
|
width: 2.0)),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 10.0,
|
height: 8.0,
|
||||||
width: 10.0,
|
width: 8.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@ -785,8 +903,8 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
width: 2.0)),
|
width: 2.0)),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
height: 10.0,
|
height: 8.0,
|
||||||
width: 10.0,
|
width: 8.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@ -971,6 +1089,83 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LastPosition extends StatefulWidget {
|
||||||
|
LastPosition({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LastPositionState createState() => _LastPositionState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LastPositionState extends State<LastPosition> {
|
||||||
|
static String _stringForSeconds(double seconds) {
|
||||||
|
if (seconds == null) return null;
|
||||||
|
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PlayHistory> getPosition(EpisodeBrief episode) async {
|
||||||
|
var dbHelper = DBHelper();
|
||||||
|
return await dbHelper.getPosition(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
|
return Selector<AudioPlayerNotifier, EpisodeBrief>(
|
||||||
|
selector: (_, audio) => audio.episode,
|
||||||
|
builder: (context, episode, child) {
|
||||||
|
return FutureBuilder<PlayHistory>(
|
||||||
|
future: getPosition(episode),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) print(snapshot.error);
|
||||||
|
return snapshot.hasData
|
||||||
|
? snapshot.data.seekValue > 0.95
|
||||||
|
? Container(
|
||||||
|
height: 20.0,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: 1,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText1
|
||||||
|
.color),
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(10.0))),
|
||||||
|
child: Text('Played before'))
|
||||||
|
: snapshot.data.seconds < 10
|
||||||
|
? Center()
|
||||||
|
: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(10.0)),
|
||||||
|
onTap: () => audio.seekTo(
|
||||||
|
(snapshot.data.seconds * 1000).toInt()),
|
||||||
|
child: Container(
|
||||||
|
width: 120.0,
|
||||||
|
height: 20.0,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
width: 1,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText1
|
||||||
|
.color),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10.0))),
|
||||||
|
child: Text('Last time ' +
|
||||||
|
_stringForSeconds(snapshot.data.seconds)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Center();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ImageRotate extends StatefulWidget {
|
class ImageRotate extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final String path;
|
final String path;
|
||||||
|
133
lib/home/download_list.dart
Normal file
133
lib/home/download_list.dart
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tsacdop/class/download_state.dart';
|
||||||
|
import 'package:tsacdop/episodes/episodedetail.dart';
|
||||||
|
import 'package:tsacdop/util/pageroute.dart';
|
||||||
|
|
||||||
|
class DownloadList extends StatefulWidget {
|
||||||
|
DownloadList({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DownloadListState createState() => _DownloadListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _downloadButton(EpisodeTask task, BuildContext context) {
|
||||||
|
var downloader = Provider.of<DownloadState>(context, listen: false);
|
||||||
|
switch (task.status.value) {
|
||||||
|
case 2:
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.pause_circle_filled,
|
||||||
|
),
|
||||||
|
onPressed: () => downloader.pauseTask(task.episode),
|
||||||
|
);
|
||||||
|
case 4:
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.refresh),
|
||||||
|
onPressed: () => downloader.retryTask(task.episode),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
onPressed: () => downloader.delTask(task.episode),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
case 6:
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(Icons.play_circle_filled),
|
||||||
|
onPressed: () => downloader.resumeTask(task.episode),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Center();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DownloadListState extends State<DownloadList> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SliverPadding(
|
||||||
|
padding: EdgeInsets.all(5.0),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
backgroundImage: FileImage(
|
||||||
|
File("${tasks[index].episode.imagePath}")),
|
||||||
|
),
|
||||||
|
trailing: _downloadButton(tasks[index], context),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
childCount: tasks.length,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(BuildContext context, int index) {
|
||||||
|
return Center();
|
||||||
|
},
|
||||||
|
childCount: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -347,7 +347,7 @@ class _PodcastPreviewState extends State<PodcastPreview> {
|
|||||||
}
|
}
|
||||||
return (snapshot.hasData)
|
return (snapshot.hasData)
|
||||||
? ShowEpisode(
|
? ShowEpisode(
|
||||||
podcast: snapshot.data,
|
episodes: snapshot.data,
|
||||||
podcastLocal: widget.podcastLocal,
|
podcastLocal: widget.podcastLocal,
|
||||||
)
|
)
|
||||||
: Center(child: CircularProgressIndicator());
|
: Center(child: CircularProgressIndicator());
|
||||||
@ -400,9 +400,9 @@ class _PodcastPreviewState extends State<PodcastPreview> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ShowEpisode extends StatelessWidget {
|
class ShowEpisode extends StatelessWidget {
|
||||||
final List<EpisodeBrief> podcast;
|
final List<EpisodeBrief> episodes;
|
||||||
final PodcastLocal podcastLocal;
|
final PodcastLocal podcastLocal;
|
||||||
ShowEpisode({Key key, this.podcast, this.podcastLocal}) : super(key: key);
|
ShowEpisode({Key key, this.episodes, this.podcastLocal}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -513,16 +513,16 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
details.globalPosition.dy),
|
details.globalPosition.dy),
|
||||||
onLongPress: () => _showPopupMenu(
|
onLongPress: () => _showPopupMenu(
|
||||||
offset,
|
offset,
|
||||||
podcast[index],
|
episodes[index],
|
||||||
context,
|
context,
|
||||||
data.item1 == podcast[index],
|
data.item1 == episodes[index],
|
||||||
data.item2.contains(podcast[index].enclosureUrl)),
|
data.item2.contains(episodes[index].enclosureUrl)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
ScaleRoute(
|
ScaleRoute(
|
||||||
page: EpisodeDetail(
|
page: EpisodeDetail(
|
||||||
episodeItem: podcast[index],
|
episodeItem: episodes[index],
|
||||||
heroTag: 'scroll',
|
heroTag: 'scroll',
|
||||||
//unique hero tag
|
//unique hero tag
|
||||||
)),
|
)),
|
||||||
@ -548,7 +548,7 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Hero(
|
Hero(
|
||||||
tag: podcast[index].enclosureUrl +
|
tag: episodes[index].enclosureUrl +
|
||||||
'scroll',
|
'scroll',
|
||||||
child: Container(
|
child: Container(
|
||||||
height: _width / 18,
|
height: _width / 18,
|
||||||
@ -560,7 +560,7 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
index < podcastLocal.upateCount
|
episodes[index].isNew == 1
|
||||||
? Text(
|
? Text(
|
||||||
'New',
|
'New',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -577,7 +577,7 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(top: 2.0),
|
padding: EdgeInsets.only(top: 2.0),
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
podcast[index].title,
|
episodes[index].title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: _width / 32,
|
fontSize: _width / 32,
|
||||||
),
|
),
|
||||||
@ -591,7 +591,7 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.bottomLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
podcast[index].dateToString(),
|
episodes[index].dateToString(),
|
||||||
//podcast[index].pubDate.substring(4, 16),
|
//podcast[index].pubDate.substring(4, 16),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: _width / 35,
|
fontSize: _width / 35,
|
||||||
@ -608,7 +608,7 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount: (podcast.length > 3) ? 3 : podcast.length,
|
childCount: (episodes.length > 3) ? 3 : episodes.length,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart' hide NestedScrollView;
|
import 'package:flutter/material.dart' hide NestedScrollView;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:tsacdop/class/download_state.dart';
|
||||||
import 'package:tsacdop/home/playlist.dart';
|
import 'package:tsacdop/home/playlist.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
|
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
|
||||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
import 'package:tsacdop/util/episodegrid.dart';
|
import 'package:tsacdop/util/episodegrid.dart';
|
||||||
import 'package:tsacdop/util/mypopupmenu.dart';
|
import 'package:tsacdop/util/mypopupmenu.dart';
|
||||||
@ -15,6 +17,7 @@ import 'package:tsacdop/util/mypopupmenu.dart';
|
|||||||
import 'package:tsacdop/home/appbar/importompl.dart';
|
import 'package:tsacdop/home/appbar/importompl.dart';
|
||||||
import 'package:tsacdop/home/audioplayer.dart';
|
import 'package:tsacdop/home/audioplayer.dart';
|
||||||
import 'home_groups.dart';
|
import 'home_groups.dart';
|
||||||
|
import 'download_list.dart';
|
||||||
|
|
||||||
class Home extends StatefulWidget {
|
class Home extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@ -58,6 +61,10 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||||||
Import(),
|
Import(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: NestedScrollView(
|
child: NestedScrollView(
|
||||||
|
innerScrollPositionKeyBuilder: () {
|
||||||
|
return Key('tab' + _controller.index.toString());
|
||||||
|
},
|
||||||
|
pinnedHeaderSliverHeightBuilder: () => 50,
|
||||||
headerSliverBuilder:
|
headerSliverBuilder:
|
||||||
(BuildContext context, bool innerBoxScrolled) {
|
(BuildContext context, bool innerBoxScrolled) {
|
||||||
return <Widget>[
|
return <Widget>[
|
||||||
@ -100,9 +107,12 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_RecentUpdate(),
|
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||||
_MyFavorite(),
|
Key('tab0'), _RecentUpdate()),
|
||||||
_MyDownload(),
|
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||||
|
Key('tab1'), _MyFavorite()),
|
||||||
|
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||||
|
Key('tab2'), _MyDownload()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -317,13 +327,11 @@ class _RecentUpdate extends StatefulWidget {
|
|||||||
_RecentUpdateState createState() => _RecentUpdateState();
|
_RecentUpdateState createState() => _RecentUpdateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecentUpdateState extends State<_RecentUpdate> {
|
class _RecentUpdateState extends State<_RecentUpdate>
|
||||||
int _updateCount = 0;
|
with AutomaticKeepAliveClientMixin {
|
||||||
Future<List<EpisodeBrief>> _getRssItem(int top) async {
|
Future<List<EpisodeBrief>> _getRssItem(int top) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
List<EpisodeBrief> episodes = await dbHelper.getRecentRssItem(top);
|
List<EpisodeBrief> episodes = await dbHelper.getRecentRssItem(top);
|
||||||
KeyValueStorage refreshcountstorage = KeyValueStorage('refreshcount');
|
|
||||||
_updateCount = await refreshcountstorage.getInt();
|
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,18 +345,18 @@ class _RecentUpdateState extends State<_RecentUpdate> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int _top;
|
int _top = 99;
|
||||||
bool _loadMore;
|
bool _loadMore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadMore = false;
|
_loadMore = false;
|
||||||
_top = 33;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
return FutureBuilder<List<EpisodeBrief>>(
|
return FutureBuilder<List<EpisodeBrief>>(
|
||||||
future: _getRssItem(_top),
|
future: _getRssItem(_top),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -367,7 +375,6 @@ class _RecentUpdateState extends State<_RecentUpdate> {
|
|||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
EpisodeGrid(
|
EpisodeGrid(
|
||||||
episodes: snapshot.data,
|
episodes: snapshot.data,
|
||||||
updateCount: _updateCount,
|
|
||||||
),
|
),
|
||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
@ -386,6 +393,9 @@ class _RecentUpdateState extends State<_RecentUpdate> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyFavorite extends StatefulWidget {
|
class _MyFavorite extends StatefulWidget {
|
||||||
@ -393,7 +403,8 @@ class _MyFavorite extends StatefulWidget {
|
|||||||
_MyFavoriteState createState() => _MyFavoriteState();
|
_MyFavoriteState createState() => _MyFavoriteState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyFavoriteState extends State<_MyFavorite> {
|
class _MyFavoriteState extends State<_MyFavorite>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
Future<List<EpisodeBrief>> _getLikedRssItem(_top) async {
|
Future<List<EpisodeBrief>> _getLikedRssItem(_top) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(_top);
|
List<EpisodeBrief> episodes = await dbHelper.getLikedRssItem(_top);
|
||||||
@ -410,18 +421,18 @@ class _MyFavoriteState extends State<_MyFavorite> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int _top;
|
int _top = 99;
|
||||||
bool _loadMore;
|
bool _loadMore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_loadMore = false;
|
_loadMore = false;
|
||||||
_top = 33;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
return FutureBuilder<List<EpisodeBrief>>(
|
return FutureBuilder<List<EpisodeBrief>>(
|
||||||
future: _getLikedRssItem(_top),
|
future: _getLikedRssItem(_top),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -436,7 +447,6 @@ class _MyFavoriteState extends State<_MyFavorite> {
|
|||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
key: PageStorageKey<String>('favorite'),
|
key: PageStorageKey<String>('favorite'),
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
EpisodeGrid(
|
EpisodeGrid(
|
||||||
episodes: snapshot.data,
|
episodes: snapshot.data,
|
||||||
@ -459,6 +469,9 @@ class _MyFavoriteState extends State<_MyFavorite> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyDownload extends StatefulWidget {
|
class _MyDownload extends StatefulWidget {
|
||||||
@ -466,32 +479,45 @@ class _MyDownload extends StatefulWidget {
|
|||||||
_MyDownloadState createState() => _MyDownloadState();
|
_MyDownloadState createState() => _MyDownloadState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyDownloadState extends State<_MyDownload> {
|
class _MyDownloadState extends State<_MyDownload>
|
||||||
Future<List<EpisodeBrief>> _getDownloadedRssItem() async {
|
with AutomaticKeepAliveClientMixin {
|
||||||
var dbHelper = DBHelper();
|
@override
|
||||||
List<EpisodeBrief> episodes = await dbHelper.getDownloadedRssItem();
|
Widget build(BuildContext context) {
|
||||||
return episodes;
|
super.build(context);
|
||||||
|
return CustomScrollView(
|
||||||
|
key: PageStorageKey<String>('downloas_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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Consumer<DownloadState>(
|
||||||
|
builder: (_, downloader, __) {
|
||||||
|
var episodes = downloader.episodeTasks
|
||||||
|
.where((task) => task.status.value == 3)
|
||||||
|
.toList()
|
||||||
|
.map((e) => e.episode)
|
||||||
|
.toList()
|
||||||
|
.reversed
|
||||||
|
.toList();
|
||||||
|
return EpisodeGrid(
|
||||||
|
episodes: episodes,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
bool get wantKeepAlive => true;
|
||||||
return FutureBuilder<List<EpisodeBrief>>(
|
|
||||||
future: _getDownloadedRssItem(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) print(snapshot.error);
|
|
||||||
return (snapshot.hasData)
|
|
||||||
? CustomScrollView(
|
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
primary: false,
|
|
||||||
slivers: <Widget>[
|
|
||||||
EpisodeGrid(
|
|
||||||
episodes: snapshot.data,
|
|
||||||
showDownload: true,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Center(child: CircularProgressIndicator());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import 'package:line_icons/line_icons.dart';
|
|||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/util/colorize.dart';
|
import 'package:tsacdop/util/colorize.dart';
|
||||||
|
import 'package:tsacdop/util/context_extension.dart';
|
||||||
|
|
||||||
class PlaylistPage extends StatefulWidget {
|
class PlaylistPage extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
@ -19,21 +20,8 @@ class PlaylistPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PlaylistPageState extends State<PlaylistPage> {
|
class _PlaylistPageState extends State<PlaylistPage> {
|
||||||
final GlobalKey<AnimatedListState> _playlistKey = GlobalKey();
|
|
||||||
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
|
||||||
|
|
||||||
Widget _episodeTag(String text, Color color) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color, borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
|
||||||
height: 23.0,
|
|
||||||
margin: EdgeInsets.only(right: 10.0),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(text, style: TextStyle(fontSize: 14.0, color: Colors.black)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int _sumPlaylistLength(List<EpisodeBrief> episodes) {
|
int _sumPlaylistLength(List<EpisodeBrief> episodes) {
|
||||||
int sum = 0;
|
int sum = 0;
|
||||||
if (episodes.length == 0) {
|
if (episodes.length == 0) {
|
||||||
@ -87,13 +75,16 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child:
|
child:
|
||||||
Selector<AudioPlayerNotifier, Tuple2<List<EpisodeBrief>, bool>>(
|
Selector<AudioPlayerNotifier, Tuple3<Playlist, bool, bool>>(
|
||||||
selector: (_, audio) =>
|
selector: (_, audio) =>
|
||||||
Tuple2(audio.queue.playlist, audio.playerRunning),
|
Tuple3(audio.queue, audio.playerRunning, audio.queueUpdate),
|
||||||
builder: (_, data, __) {
|
builder: (_, data, __) {
|
||||||
|
print('update');
|
||||||
|
final List<EpisodeBrief> episodes = data.item1.playlist;
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
height: _topHeight,
|
height: _topHeight,
|
||||||
@ -115,7 +106,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
),
|
),
|
||||||
children: <TextSpan>[
|
children: <TextSpan>[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: data.item1.length.toString(),
|
text: episodes.length.toString(),
|
||||||
style: GoogleFonts.cairo(
|
style: GoogleFonts.cairo(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
@ -124,7 +115,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: data.item1.length < 2
|
text: episodes.length < 2
|
||||||
? ' episode '
|
? ' episode '
|
||||||
: ' episodes ',
|
: ' episodes ',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -133,7 +124,7 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
)),
|
)),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text:
|
text:
|
||||||
_sumPlaylistLength(data.item1).toString(),
|
_sumPlaylistLength(episodes).toString(),
|
||||||
style: GoogleFonts.cairo(
|
style: GoogleFonts.cairo(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
@ -179,171 +170,24 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
height: 3,
|
height: 3,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AnimatedList(
|
child: ReorderableListView(
|
||||||
controller: _controller,
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
key: _playlistKey,
|
if (newIndex > oldIndex) {
|
||||||
shrinkWrap: true,
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final EpisodeBrief episodeRemove =
|
||||||
|
episodes[oldIndex];
|
||||||
|
audio.delFromPlaylist(episodeRemove);
|
||||||
|
audio.addToPlaylistAt(episodeRemove, newIndex);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
initialItemCount: data.item1.length,
|
children: episodes
|
||||||
itemBuilder: (context, index, animation) {
|
.map<Widget>((episode) => DismissibleContainer(
|
||||||
Color _c = (Theme.of(context).brightness ==
|
episode: episode,
|
||||||
Brightness.light)
|
key: ValueKey(episode.enclosureUrl),
|
||||||
? data.item1[index].primaryColor.colorizedark()
|
))
|
||||||
: data.item1[index].primaryColor.colorizeLight();
|
.toList()),
|
||||||
return ScaleTransition(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
scale: animation,
|
|
||||||
child: Dismissible(
|
|
||||||
background: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.red),
|
|
||||||
padding: EdgeInsets.all(5),
|
|
||||||
child: Icon(
|
|
||||||
LineIcons.trash_alt_solid,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
color: Colors.red),
|
|
||||||
padding: EdgeInsets.all(5),
|
|
||||||
child: Icon(
|
|
||||||
LineIcons.trash_alt_solid,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 15,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
height: 50,
|
|
||||||
color: Theme.of(context).accentColor,
|
|
||||||
),
|
|
||||||
key: Key(data.item1[index].enclosureUrl),
|
|
||||||
onDismissed: (direction) async {
|
|
||||||
await audio.delFromPlaylist(data.item1[index]);
|
|
||||||
_playlistKey.currentState.removeItem(
|
|
||||||
index, (context, animation) => Center());
|
|
||||||
Fluttertoast.showToast(
|
|
||||||
msg: 'Removed From Playlist',
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
ListTile(
|
|
||||||
title: Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
top: 10.0, bottom: 5.0),
|
|
||||||
child: Text(
|
|
||||||
data.item1[index].title,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
backgroundColor: _c.withOpacity(0.5),
|
|
||||||
backgroundImage: FileImage(File(
|
|
||||||
"${data.item1[index].imagePath}")),
|
|
||||||
),
|
|
||||||
trailing: index == 0
|
|
||||||
? data.item2
|
|
||||||
? Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
right: 15, top: 20),
|
|
||||||
child: SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 15,
|
|
||||||
child: WaveLoader()),
|
|
||||||
)
|
|
||||||
: IconButton(
|
|
||||||
icon: Icon(
|
|
||||||
Icons.play_arrow,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.accentColor,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
audio.playlistLoad())
|
|
||||||
: Transform.rotate(
|
|
||||||
angle: math.pi,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: 'Move to Top',
|
|
||||||
icon: Icon(
|
|
||||||
LineIcons.download_solid),
|
|
||||||
onPressed: () async {
|
|
||||||
await audio.moveToTop(
|
|
||||||
data.item1[index]);
|
|
||||||
_playlistKey.currentState
|
|
||||||
.removeItem(
|
|
||||||
index,
|
|
||||||
(context,
|
|
||||||
animation) =>
|
|
||||||
Container());
|
|
||||||
data.item2
|
|
||||||
? _playlistKey
|
|
||||||
.currentState
|
|
||||||
.insertItem(1)
|
|
||||||
: _playlistKey
|
|
||||||
.currentState
|
|
||||||
.insertItem(0);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
subtitle: Container(
|
|
||||||
padding:
|
|
||||||
EdgeInsets.only(top: 5, bottom: 10),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
(data.item1[index].explicit == 1)
|
|
||||||
? Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red[800],
|
|
||||||
shape: BoxShape.circle),
|
|
||||||
height: 20.0,
|
|
||||||
width: 20.0,
|
|
||||||
margin: EdgeInsets.only(
|
|
||||||
right: 10.0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text('E',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white)))
|
|
||||||
: Center(),
|
|
||||||
data.item1[index].duration != 0
|
|
||||||
? _episodeTag(
|
|
||||||
(data.item1[index].duration)
|
|
||||||
.toString() +
|
|
||||||
'mins',
|
|
||||||
Colors.cyan[300])
|
|
||||||
: Center(),
|
|
||||||
data.item1[index].enclosureLength !=
|
|
||||||
null
|
|
||||||
? _episodeTag(
|
|
||||||
((data.item1[index]
|
|
||||||
.enclosureLength) ~/
|
|
||||||
1000000)
|
|
||||||
.toString() +
|
|
||||||
'MB',
|
|
||||||
Colors.lightBlue[300])
|
|
||||||
: Center(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Divider(
|
|
||||||
height: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -354,3 +198,152 @@ class _PlaylistPageState extends State<PlaylistPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DismissibleContainer extends StatefulWidget {
|
||||||
|
final EpisodeBrief episode;
|
||||||
|
DismissibleContainer({this.episode, Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DismissibleContainerState createState() => _DismissibleContainerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DismissibleContainerState extends State<DismissibleContainer> {
|
||||||
|
bool _delete;
|
||||||
|
Widget _episodeTag(String text, Color color) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color, borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||||
|
height: 23.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(text, style: TextStyle(fontSize: 14.0, color: Colors.black)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_delete = false;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: Duration(milliseconds: 300),
|
||||||
|
height: _delete ? 0 : 95.0,
|
||||||
|
child: _delete
|
||||||
|
? Container(
|
||||||
|
color: context.accentColor,
|
||||||
|
)
|
||||||
|
: Dismissible(
|
||||||
|
key: ValueKey(widget.episode.enclosureUrl + 't'),
|
||||||
|
background: Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle, color: Colors.red),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.trash_alt_solid,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle, color: Colors.red),
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Icon(
|
||||||
|
LineIcons.trash_alt_solid,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
height: 50,
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
),
|
||||||
|
onDismissed: (direction) async {
|
||||||
|
setState(() {
|
||||||
|
_delete = true;
|
||||||
|
});
|
||||||
|
int index = await audio.delFromPlaylist(widget.episode);
|
||||||
|
final episodeRemove = widget.episode;
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Removed From Playlist',
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
|
content: Text('1 episode removed'),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: 'Undo',
|
||||||
|
onPressed: () {
|
||||||
|
audio.addToPlaylistAt(episodeRemove, index);
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Container(
|
||||||
|
padding: EdgeInsets.only(top: 10.0, bottom: 5.0),
|
||||||
|
child: Text(
|
||||||
|
widget.episode.title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
//backgroundColor: _c.withOpacity(0.5),
|
||||||
|
backgroundImage:
|
||||||
|
FileImage(File("${widget.episode.imagePath}")),
|
||||||
|
),
|
||||||
|
subtitle: Container(
|
||||||
|
padding: EdgeInsets.only(top: 5, bottom: 10),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
(widget.episode.explicit == 1)
|
||||||
|
? Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red[800],
|
||||||
|
shape: BoxShape.circle),
|
||||||
|
height: 20.0,
|
||||||
|
width: 20.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text('E',
|
||||||
|
style: TextStyle(color: Colors.white)))
|
||||||
|
: Center(),
|
||||||
|
widget.episode.duration != 0
|
||||||
|
? _episodeTag(
|
||||||
|
(widget.episode.duration).toString() + 'mins',
|
||||||
|
Colors.cyan[300])
|
||||||
|
: Center(),
|
||||||
|
widget.episode.enclosureLength != null
|
||||||
|
? _episodeTag(
|
||||||
|
((widget.episode.enclosureLength) ~/ 1000000)
|
||||||
|
.toString() +
|
||||||
|
'MB',
|
||||||
|
Colors.lightBlue[300])
|
||||||
|
: Center(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Divider(
|
||||||
|
// height: 2,
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,9 +28,10 @@ class DBHelper {
|
|||||||
void _onCreate(Database db, int version) async {
|
void _onCreate(Database db, int version) async {
|
||||||
await db
|
await db
|
||||||
.execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT,
|
.execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT,
|
||||||
imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT,
|
imageUrl TEXT,rssUrl TEXT UNIQUE, primaryColor TEXT, author TEXT,
|
||||||
description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT,
|
description TEXT, add_date INTEGER, imagePath TEXT, provider TEXT, link TEXT,
|
||||||
background_image TEXT DEFAULT '',hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0)""");
|
background_image TEXT DEFAULT '',hosts TEXT DEFAULT '',update_count INTEGER DEFAULT 0,
|
||||||
|
episode_count INTEGER DEFAULT 0)""");
|
||||||
await db
|
await db
|
||||||
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
|
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
|
||||||
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
|
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
|
||||||
@ -52,10 +53,9 @@ class DBHelper {
|
|||||||
await Future.forEach(podcasts, (s) async {
|
await Future.forEach(podcasts, (s) async {
|
||||||
List<Map> list;
|
List<Map> list;
|
||||||
list = await dbClient.rawQuery(
|
list = await dbClient.rawQuery(
|
||||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider, link ,update_count FROM PodcastLocal WHERE id = ?',
|
"""SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath , provider,
|
||||||
|
link ,update_count, episode_count FROM PodcastLocal WHERE id = ?""",
|
||||||
[s]);
|
[s]);
|
||||||
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
|
||||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [s]));
|
|
||||||
podcastLocal.add(PodcastLocal(
|
podcastLocal.add(PodcastLocal(
|
||||||
list.first['title'],
|
list.first['title'],
|
||||||
list.first['imageUrl'],
|
list.first['imageUrl'],
|
||||||
@ -67,7 +67,7 @@ class DBHelper {
|
|||||||
list.first['provider'],
|
list.first['provider'],
|
||||||
list.first['link'],
|
list.first['link'],
|
||||||
upateCount: list.first['update_count'],
|
upateCount: list.first['update_count'],
|
||||||
episodeCount: count));
|
episodeCount: list.first['episode_count']));
|
||||||
});
|
});
|
||||||
return podcastLocal;
|
return podcastLocal;
|
||||||
}
|
}
|
||||||
@ -94,6 +94,14 @@ class DBHelper {
|
|||||||
return podcastLocal;
|
return podcastLocal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<int>> getPodcastCounts(String id) async {
|
||||||
|
var dbClient = await database;
|
||||||
|
List<Map> list = await dbClient.rawQuery(
|
||||||
|
'SELECT update_count, episode_count FROM PodcastLocal WHERE id = ?',
|
||||||
|
[id]);
|
||||||
|
return [list.first['update_count'], list.first['episode_count']];
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> checkPodcast(String url) async {
|
Future<bool> checkPodcast(String url) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
List<Map> list = await dbClient
|
List<Map> list = await dbClient
|
||||||
@ -122,8 +130,6 @@ class DBHelper {
|
|||||||
podcastLocal.provider,
|
podcastLocal.provider,
|
||||||
podcastLocal.link
|
podcastLocal.link
|
||||||
]);
|
]);
|
||||||
});
|
|
||||||
await dbClient.transaction((txn) async {
|
|
||||||
await txn.rawInsert(
|
await txn.rawInsert(
|
||||||
"""REPLACE INTO SubscribeHistory(id, title, rss_url, add_date) VALUES (?, ?, ?, ?)""",
|
"""REPLACE INTO SubscribeHistory(id, title, rss_url, add_date) VALUES (?, ?, ?, ?)""",
|
||||||
[
|
[
|
||||||
@ -240,12 +246,17 @@ class DBHelper {
|
|||||||
return (sum ~/ 60).toDouble();
|
return (sum ~/ 60).toDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getPosition(EpisodeBrief episodeBrief) async {
|
Future<PlayHistory> getPosition(EpisodeBrief episodeBrief) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"SELECT seconds FROM PlayHistory Where enclosure_url = ?",
|
"SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory Where enclosure_url = ?",
|
||||||
[episodeBrief.enclosureUrl]);
|
[episodeBrief.enclosureUrl]);
|
||||||
return list.length > 0 ? list.first['seconds'] : 0;
|
return list.length > 0
|
||||||
|
? PlayHistory(list.first['title'], list.first['enclosure_url'],
|
||||||
|
list.first['seconds'], list.first['seek_value'],
|
||||||
|
playdate:
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(list.first['add_date']))
|
||||||
|
: PlayHistory(episodeBrief.title, episodeBrief.enclosureUrl, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime _parsePubDate(String pubDate) {
|
DateTime _parsePubDate(String pubDate) {
|
||||||
@ -335,13 +346,6 @@ class DBHelper {
|
|||||||
print(feed.items[i].title);
|
print(feed.items[i].title);
|
||||||
description = _getDescription(feed.items[i].content.value ?? '',
|
description = _getDescription(feed.items[i].content.value ?? '',
|
||||||
feed.items[i].description ?? '', feed.items[i].itunes.summary ?? '');
|
feed.items[i].description ?? '', feed.items[i].itunes.summary ?? '');
|
||||||
// if (feed.items[i].itunes.summary != null) {
|
|
||||||
// feed.items[i].itunes.summary.contains('<')
|
|
||||||
// ? description = feed.items[i].itunes.summary
|
|
||||||
// : description = feed.items[i].description;
|
|
||||||
// } else {
|
|
||||||
// description = feed.items[i].description;
|
|
||||||
// }
|
|
||||||
if (feed.items[i].enclosure != null) {
|
if (feed.items[i].enclosure != null) {
|
||||||
_isXimalaya(feed.items[i].enclosure.url)
|
_isXimalaya(feed.items[i].enclosure.url)
|
||||||
? url = feed.items[i].enclosure.url.split('=').last
|
? url = feed.items[i].enclosure.url.split('=').last
|
||||||
@ -377,6 +381,12 @@ class DBHelper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
int countUpdate = Sqflite.firstIntValue(await dbClient
|
||||||
|
.rawQuery('SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [id]));
|
||||||
|
|
||||||
|
await dbClient.rawUpdate(
|
||||||
|
"""UPDATE PodcastLocal SET episode_count = ? WHERE id = ?""",
|
||||||
|
[countUpdate, id]);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,57 +401,56 @@ class DBHelper {
|
|||||||
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
int count = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||||
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [podcastLocal.id]));
|
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [podcastLocal.id]));
|
||||||
|
|
||||||
print(count);
|
|
||||||
await dbClient.rawUpdate(
|
await dbClient.rawUpdate(
|
||||||
"""UPDATE PodcastLocal SET update_count = ? WHERE id = ?""",
|
"UPDATE Episodes SET is_new = 0 WHERE feed_id = ?", [podcastLocal.id]);
|
||||||
[(result - count), podcastLocal.id]);
|
|
||||||
if (count == result) {
|
|
||||||
result = 0;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i < (result - count); i++) {
|
|
||||||
print(feed.items[i].title);
|
|
||||||
description = _getDescription(
|
|
||||||
feed.items[i].content.value ?? '',
|
|
||||||
feed.items[i].description ?? '',
|
|
||||||
feed.items[i].itunes.summary ?? '');
|
|
||||||
|
|
||||||
if (feed.items[i].enclosure?.url != null) {
|
for (int i = 0; i < result; i++) {
|
||||||
_isXimalaya(feed.items[i].enclosure.url)
|
print(feed.items[i].title);
|
||||||
? url = feed.items[i].enclosure.url.split('=').last
|
description = _getDescription(feed.items[i].content.value ?? '',
|
||||||
: url = feed.items[i].enclosure.url;
|
feed.items[i].description ?? '', feed.items[i].itunes.summary ?? '');
|
||||||
}
|
|
||||||
|
|
||||||
final title = feed.items[i].itunes.title ?? feed.items[i].title;
|
if (feed.items[i].enclosure?.url != null) {
|
||||||
final length = feed.items[i]?.enclosure?.length ?? 0;
|
_isXimalaya(feed.items[i].enclosure.url)
|
||||||
final pubDate = feed.items[i].pubDate;
|
? url = feed.items[i].enclosure.url.split('=').last
|
||||||
final date = _parsePubDate(pubDate);
|
: url = feed.items[i].enclosure.url;
|
||||||
final milliseconds = date.millisecondsSinceEpoch;
|
}
|
||||||
final duration = feed.items[i].itunes.duration?.inMinutes ?? 0;
|
|
||||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
final title = feed.items[i].itunes.title ?? feed.items[i].title;
|
||||||
|
final length = feed.items[i]?.enclosure?.length ?? 0;
|
||||||
if (url != null) {
|
final pubDate = feed.items[i].pubDate;
|
||||||
await dbClient.transaction((txn) {
|
final date = _parsePubDate(pubDate);
|
||||||
return txn.rawInsert(
|
final milliseconds = date.millisecondsSinceEpoch;
|
||||||
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
final duration = feed.items[i].itunes.duration?.inMinutes ?? 0;
|
||||||
description, feed_id, milliseconds, duration, explicit, media_id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||||
[
|
|
||||||
title,
|
if (url != null) {
|
||||||
url,
|
await dbClient.transaction((txn) async {
|
||||||
length,
|
int id = await txn.rawInsert(
|
||||||
pubDate,
|
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||||
description,
|
description, feed_id, milliseconds, duration, explicit, media_id, is_new) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)""",
|
||||||
podcastLocal.id,
|
[
|
||||||
milliseconds,
|
title,
|
||||||
duration,
|
url,
|
||||||
explicit,
|
length,
|
||||||
url
|
pubDate,
|
||||||
]);
|
description,
|
||||||
});
|
podcastLocal.id,
|
||||||
}
|
milliseconds,
|
||||||
|
duration,
|
||||||
|
explicit,
|
||||||
|
url,
|
||||||
|
]);
|
||||||
|
print("$id");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return (result - count) < 0 ? 0 : (result - count);
|
|
||||||
}
|
}
|
||||||
|
int countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||||
|
'SELECT COUNT(*) FROM Episodes WHERE feed_id = ?', [podcastLocal.id]));
|
||||||
|
|
||||||
|
await dbClient.rawUpdate(
|
||||||
|
"""UPDATE PodcastLocal SET update_count = ?, episode_count = ? WHERE id = ?""",
|
||||||
|
[countUpdate - count, countUpdate, podcastLocal.id]);
|
||||||
|
return countUpdate - count;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<EpisodeBrief>> getRssItem(String id, int i) async {
|
Future<List<EpisodeBrief>> getRssItem(String id, int i) async {
|
||||||
@ -450,7 +459,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient
|
List<Map> list = await dbClient
|
||||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
.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.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
|
||||||
E.downloaded, P.primaryColor , E.media_id
|
E.downloaded, P.primaryColor , E.media_id, E.is_new
|
||||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]);
|
WHERE P.id = ? ORDER BY E.milliseconds DESC LIMIT ?""", [id, i]);
|
||||||
for (int x = 0; x < list.length; x++) {
|
for (int x = 0; x < list.length; x++) {
|
||||||
@ -466,7 +475,8 @@ class DBHelper {
|
|||||||
list[x]['duration'],
|
list[x]['duration'],
|
||||||
list[x]['explicit'],
|
list[x]['explicit'],
|
||||||
list[x]['imagePath'],
|
list[x]['imagePath'],
|
||||||
list[x]['media_id']));
|
list[x]['media_id'],
|
||||||
|
list[x]['is_new']));
|
||||||
}
|
}
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
@ -477,7 +487,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient
|
List<Map> list = await dbClient
|
||||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
.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.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||||
E.downloaded, P.primaryColor, E.media_id
|
E.downloaded, P.primaryColor, E.media_id, E.is_new
|
||||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
where E.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 3""", [id]);
|
where E.feed_id = ? ORDER BY E.milliseconds DESC LIMIT 3""", [id]);
|
||||||
for (int x = 0; x < list.length; x++) {
|
for (int x = 0; x < list.length; x++) {
|
||||||
@ -493,7 +503,8 @@ class DBHelper {
|
|||||||
list[x]['duration'],
|
list[x]['duration'],
|
||||||
list[x]['explicit'],
|
list[x]['explicit'],
|
||||||
list[x]['imagePath'],
|
list[x]['imagePath'],
|
||||||
list[x]['media_id']));
|
list[x]['media_id'],
|
||||||
|
list[x]['is_new']));
|
||||||
}
|
}
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
@ -504,7 +515,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
"""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.milliseconds, P.imagePath, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||||
E.downloaded, P.primaryColor, E.media_id
|
E.downloaded, P.primaryColor, E.media_id, E.is_new
|
||||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
|
where E.enclosure_url = ? ORDER BY E.milliseconds DESC LIMIT 3""",
|
||||||
[url]);
|
[url]);
|
||||||
@ -522,7 +533,8 @@ class DBHelper {
|
|||||||
list.first['duration'],
|
list.first['duration'],
|
||||||
list.first['explicit'],
|
list.first['explicit'],
|
||||||
list.first['imagePath'],
|
list.first['imagePath'],
|
||||||
list.first['media_id']);
|
list.first['media_id'],
|
||||||
|
list.first['is_new']);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,7 +544,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient
|
List<Map> list = await dbClient
|
||||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||||
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
|
E.milliseconds, P.title as feed_title, E.duration, E.explicit, E.liked,
|
||||||
E.downloaded, P.imagePath, P.primaryColor, E.media_id
|
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
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
|
ORDER BY E.milliseconds DESC LIMIT ? """, [top]);
|
||||||
for (int x = 0; x < list.length; x++) {
|
for (int x = 0; x < list.length; x++) {
|
||||||
@ -548,7 +560,8 @@ class DBHelper {
|
|||||||
list[x]['duration'],
|
list[x]['duration'],
|
||||||
list[x]['explicit'],
|
list[x]['explicit'],
|
||||||
list[x]['imagePath'],
|
list[x]['imagePath'],
|
||||||
list[x]['media_id']));
|
list[x]['media_id'],
|
||||||
|
list[x]['is_new']));
|
||||||
}
|
}
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
@ -559,8 +572,8 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""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.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||||
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
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]);
|
WHERE E.liked = 1 ORDER BY E.milliseconds DESC LIMIT ?""", [i]);
|
||||||
for (int x = 0; x < list.length; x++) {
|
for (int x = 0; x < list.length; x++) {
|
||||||
episodes.add(EpisodeBrief(
|
episodes.add(EpisodeBrief(
|
||||||
list[x]['title'],
|
list[x]['title'],
|
||||||
@ -574,7 +587,8 @@ class DBHelper {
|
|||||||
list[x]['duration'],
|
list[x]['duration'],
|
||||||
list[x]['explicit'],
|
list[x]['explicit'],
|
||||||
list[x]['imagePath'],
|
list[x]['imagePath'],
|
||||||
list[x]['media_id']));
|
list[x]['media_id'],
|
||||||
|
list[x]['is_new']));
|
||||||
}
|
}
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
@ -597,19 +611,19 @@ class DBHelper {
|
|||||||
|
|
||||||
Future<int> saveDownloaded(String url, String id) async {
|
Future<int> saveDownloaded(String url, String id) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||||
int count = await dbClient.rawUpdate(
|
int count = await dbClient.rawUpdate(
|
||||||
"UPDATE Episodes SET downloaded = ?, download_date = ? WHERE enclosure_url = ?",
|
"UPDATE Episodes SET downloaded = ?, download_date = ? WHERE enclosure_url = ?",
|
||||||
[id, _milliseconds, url]);
|
[id, milliseconds, url]);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> saveMediaId(String url, String path) async {
|
Future<int> saveMediaId(String url, String path, String id) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
int milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||||
int count = await dbClient.rawUpdate(
|
int count = await dbClient.rawUpdate(
|
||||||
"UPDATE Episodes SET media_id = ?, download_date = ? WHERE enclosure_url = ?",
|
"UPDATE Episodes SET media_id = ?, download_date = ?, downloaded = ? WHERE enclosure_url = ?",
|
||||||
[path, _milliseconds, url]);
|
[path, milliseconds, id, url]);
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,8 +642,8 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""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.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||||
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
P.primaryColor, E.media_id, E.is_new FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC LIMIT 99""");
|
WHERE E.downloaded != 'ND' ORDER BY E.download_date DESC""");
|
||||||
for (int x = 0; x < list.length; x++) {
|
for (int x = 0; x < list.length; x++) {
|
||||||
episodes.add(EpisodeBrief(
|
episodes.add(EpisodeBrief(
|
||||||
list[x]['title'],
|
list[x]['title'],
|
||||||
@ -643,7 +657,8 @@ class DBHelper {
|
|||||||
list[x]['duration'],
|
list[x]['duration'],
|
||||||
list[x]['explicit'],
|
list[x]['explicit'],
|
||||||
list[x]['imagePath'],
|
list[x]['imagePath'],
|
||||||
list[x]['media_id']));
|
list[x]['media_id'],
|
||||||
|
list[x]['is_new']));
|
||||||
}
|
}
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
@ -670,7 +685,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""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.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||||
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
P.primaryColor, E.media_id, E.is_new FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
WHERE E.enclosure_url = ?""", [url]);
|
WHERE E.enclosure_url = ?""", [url]);
|
||||||
if (list.length == 0) {
|
if (list.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
@ -687,7 +702,8 @@ class DBHelper {
|
|||||||
list.first['duration'],
|
list.first['duration'],
|
||||||
list.first['explicit'],
|
list.first['explicit'],
|
||||||
list.first['imagePath'],
|
list.first['imagePath'],
|
||||||
list.first['media_id']);
|
list.first['media_id'],
|
||||||
|
list.first['is_new']);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -698,7 +714,7 @@ class DBHelper {
|
|||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""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.title as feed_title, E.duration, E.explicit, E.liked, E.downloaded,
|
||||||
P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
P.primaryColor, E.media_id, E.is_new FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
WHERE E.media_id = ?""", [id]);
|
WHERE E.media_id = ?""", [id]);
|
||||||
episode = EpisodeBrief(
|
episode = EpisodeBrief(
|
||||||
list.first['title'],
|
list.first['title'],
|
||||||
@ -712,7 +728,8 @@ class DBHelper {
|
|||||||
list.first['duration'],
|
list.first['duration'],
|
||||||
list.first['explicit'],
|
list.first['explicit'],
|
||||||
list.first['imagePath'],
|
list.first['imagePath'],
|
||||||
list.first['media_id']);
|
list.first['media_id'],
|
||||||
|
list.first['is_new']);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,13 @@ import 'package:tsacdop/home/appbar/addpodcast.dart';
|
|||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/importompl.dart';
|
import 'package:tsacdop/class/importompl.dart';
|
||||||
import 'package:tsacdop/class/settingstate.dart';
|
import 'package:tsacdop/class/settingstate.dart';
|
||||||
|
import 'package:tsacdop/class/download_state.dart';
|
||||||
|
|
||||||
final SettingState themeSetting = SettingState();
|
final SettingState themeSetting = SettingState();
|
||||||
Future main() async {
|
Future main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await themeSetting.initData();
|
await themeSetting.initData();
|
||||||
await FlutterDownloader.initialize();
|
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@ -21,13 +22,16 @@ Future main() async {
|
|||||||
ChangeNotifierProvider(create: (_) => AudioPlayerNotifier()),
|
ChangeNotifierProvider(create: (_) => AudioPlayerNotifier()),
|
||||||
ChangeNotifierProvider(create: (_) => GroupList()),
|
ChangeNotifierProvider(create: (_) => GroupList()),
|
||||||
ChangeNotifierProvider(create: (_) => ImportOmpl()),
|
ChangeNotifierProvider(create: (_) => ImportOmpl()),
|
||||||
|
ChangeNotifierProvider(create: (_) => DownloadState(),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
child: MyApp(),
|
child: MyApp(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
await FlutterDownloader.initialize();
|
||||||
SystemUiOverlayStyle systemUiOverlayStyle =
|
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
|
||||||
SystemUiOverlayStyle(statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent);
|
statusBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarColor: Colors.transparent);
|
||||||
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
|
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
|
||||||
|
|
||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations(
|
||||||
@ -67,7 +71,7 @@ class MyApp extends StatelessWidget {
|
|||||||
darkTheme: ThemeData.dark().copyWith(
|
darkTheme: ThemeData.dark().copyWith(
|
||||||
accentColor: setting.accentSetColor,
|
accentColor: setting.accentSetColor,
|
||||||
primaryColorDark: Colors.grey[800],
|
primaryColorDark: Colors.grey[800],
|
||||||
// scaffoldBackgroundColor: Colors.black87,
|
// scaffoldBackgroundColor: Colors.black87,
|
||||||
appBarTheme: AppBarTheme(elevation: 0),
|
appBarTheme: AppBarTheme(elevation: 0),
|
||||||
),
|
),
|
||||||
home: MyHomePage(),
|
home: MyHomePage(),
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
|
import 'package:tsacdop/class/podcast_group.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||||
@ -35,15 +36,18 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||||||
Future _updateRssItem(PodcastLocal podcastLocal) async {
|
Future _updateRssItem(PodcastLocal podcastLocal) async {
|
||||||
var dbHelper = DBHelper();
|
var dbHelper = DBHelper();
|
||||||
final result = await dbHelper.updatePodcastRss(podcastLocal);
|
final result = await dbHelper.updatePodcastRss(podcastLocal);
|
||||||
result == 0
|
if(result == 0)
|
||||||
? Fluttertoast.showToast(
|
{ Fluttertoast.showToast(
|
||||||
msg: 'No Update',
|
msg: 'No Update',
|
||||||
gravity: ToastGravity.TOP,
|
gravity: ToastGravity.TOP,
|
||||||
)
|
);}
|
||||||
: Fluttertoast.showToast(
|
else{
|
||||||
|
Fluttertoast.showToast(
|
||||||
msg: 'Updated $result Episodes',
|
msg: 'Updated $result Episodes',
|
||||||
gravity: ToastGravity.TOP,
|
gravity: ToastGravity.TOP,
|
||||||
);
|
);
|
||||||
|
Provider.of<GroupList>(context, listen: false).updatePodcast(podcastLocal);
|
||||||
|
}
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,8 +404,6 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||||||
episodes: snapshot.data,
|
episodes: snapshot.data,
|
||||||
showFavorite: true,
|
showFavorite: true,
|
||||||
showNumber: true,
|
showNumber: true,
|
||||||
updateCount:
|
|
||||||
widget.podcastLocal.upateCount,
|
|
||||||
episodeCount:
|
episodeCount:
|
||||||
widget.podcastLocal.episodeCount,
|
widget.podcastLocal.episodeCount,
|
||||||
),
|
),
|
||||||
|
@ -40,7 +40,7 @@ class _PodcastGroupListState extends State<PodcastGroupList> {
|
|||||||
widget.group.podcasts.insert(newIndex, podcast);
|
widget.group.podcasts.insert(newIndex, podcast);
|
||||||
});
|
});
|
||||||
widget.group.setOrderedPodcasts = widget.group.podcasts;
|
widget.group.setOrderedPodcasts = widget.group.podcasts;
|
||||||
groupList.addToOrderChanged(widget.group.name);
|
groupList.addToOrderChanged(widget.group);
|
||||||
},
|
},
|
||||||
children: widget.group.podcasts
|
children: widget.group.podcasts
|
||||||
.map<Widget>((PodcastLocal podcastLocal) {
|
.map<Widget>((PodcastLocal podcastLocal) {
|
||||||
@ -319,7 +319,7 @@ class _PodcastCardState extends State<PodcastCard> {
|
|||||||
bottom: 20),
|
bottom: 20),
|
||||||
title: Text('Remove confirm'),
|
title: Text('Remove confirm'),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Are you sure you want to unsubscribe?'),
|
'Are you sure you want to unsubscribe?'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
|
@ -72,7 +72,7 @@ class _PodcastManageState extends State<PodcastManage>
|
|||||||
Widget _saveButton(BuildContext context) {
|
Widget _saveButton(BuildContext context) {
|
||||||
return Consumer<GroupList>(
|
return Consumer<GroupList>(
|
||||||
builder: (_, groupList, __) {
|
builder: (_, groupList, __) {
|
||||||
if (groupList.orderChanged.contains(groupList.groups[_index].name)) {
|
if (groupList.orderChanged.contains(groupList.groups[_index])) {
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
} else if (_fraction > 0) {
|
} else if (_fraction > 0) {
|
||||||
_controller.reverse();
|
_controller.reverse();
|
||||||
@ -92,8 +92,8 @@ class _PodcastManageState extends State<PodcastManage>
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.grey[700],
|
color: Colors.grey[700].withOpacity(0.5),
|
||||||
blurRadius: 5,
|
blurRadius: 1,
|
||||||
offset: Offset(1, 1),
|
offset: Offset(1, 1),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
@ -161,298 +161,304 @@ class _PodcastManageState extends State<PodcastManage>
|
|||||||
OrderMenu(),
|
OrderMenu(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Consumer<GroupList>(builder: (_, groupList, __) {
|
body: WillPopScope(
|
||||||
bool _isLoading = groupList.isLoading;
|
onWillPop: () async {
|
||||||
List<PodcastGroup> _groups = groupList.groups;
|
await Provider.of<GroupList>(context, listen: false).clearOrderChanged();
|
||||||
return _isLoading
|
return true;
|
||||||
? Center()
|
},
|
||||||
: Stack(
|
child: Consumer<GroupList>(builder: (_, groupList, __) {
|
||||||
children: <Widget>[
|
bool _isLoading = groupList.isLoading;
|
||||||
CustomTabView(
|
List<PodcastGroup> _groups = groupList.groups;
|
||||||
itemCount: _groups.length,
|
return _isLoading
|
||||||
tabBuilder: (context, index) => Tab(
|
? Center()
|
||||||
child: Container(
|
: Stack(
|
||||||
height: 30.0,
|
children: <Widget>[
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
CustomTabView(
|
||||||
alignment: Alignment.center,
|
itemCount: _groups.length,
|
||||||
decoration: BoxDecoration(
|
tabBuilder: (context, index) => Tab(
|
||||||
color: (_scroll - index).abs() > 1
|
child: Container(
|
||||||
? Colors.grey[300]
|
height: 30.0,
|
||||||
: Colors.grey[300]
|
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
.withOpacity((_scroll - index).abs()),
|
alignment: Alignment.center,
|
||||||
borderRadius:
|
decoration: BoxDecoration(
|
||||||
BorderRadius.all(Radius.circular(15)),
|
color: (_scroll - index).abs() > 1
|
||||||
),
|
? Colors.grey[300]
|
||||||
child: Text(
|
: Colors.grey[300]
|
||||||
_groups[index].name,
|
.withOpacity((_scroll - index).abs()),
|
||||||
)),
|
borderRadius:
|
||||||
),
|
BorderRadius.all(Radius.circular(15)),
|
||||||
pageBuilder: (context, index) => Container(
|
|
||||||
key: ObjectKey(_groups[index].name),
|
|
||||||
child: PodcastGroupList(group: _groups[index])),
|
|
||||||
onPositionChange: (value) =>
|
|
||||||
setState(() => _index = value),
|
|
||||||
onScroll: (value) => setState(() => _scroll = value),
|
|
||||||
),
|
|
||||||
_showSetting
|
|
||||||
? Positioned.fill(
|
|
||||||
top: 50,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
await _menuController.reverse();
|
|
||||||
setState(() => _showSetting = false);
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.scaffoldBackgroundColor
|
|
||||||
.withOpacity(0.5 * _menuController.value),
|
|
||||||
),
|
),
|
||||||
),
|
child: Text(
|
||||||
)
|
_groups[index].name,
|
||||||
: Center(),
|
)),
|
||||||
Positioned(
|
),
|
||||||
right: 30,
|
pageBuilder: (context, index) => Container(
|
||||||
bottom: 30,
|
key: ValueKey(_groups[index].name),
|
||||||
child: _saveButton(context),
|
child: PodcastGroupList(group: _groups[index])),
|
||||||
),
|
onPositionChange: (value) =>
|
||||||
_showSetting
|
setState(() => _index = value),
|
||||||
? Positioned(
|
onScroll: (value) => setState(() => _scroll = value),
|
||||||
right: 30 * _menuValue,
|
),
|
||||||
bottom: 100,
|
_showSetting
|
||||||
child: Container(
|
? Positioned.fill(
|
||||||
alignment: Alignment.centerRight,
|
top: 50,
|
||||||
child: Column(
|
child: GestureDetector(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
onTap: () async {
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
await _menuController.reverse();
|
||||||
children: <Widget>[
|
setState(() => _showSetting = false);
|
||||||
Material(
|
},
|
||||||
color: Colors.transparent,
|
child: Container(
|
||||||
child: InkWell(
|
color: Theme.of(context)
|
||||||
onTap: () {
|
.scaffoldBackgroundColor
|
||||||
_menuController.reverse();
|
.withOpacity(0.5 * _menuController.value),
|
||||||
setState(() => _showSetting = false);
|
),
|
||||||
_index == 0
|
),
|
||||||
? Fluttertoast.showToast(
|
)
|
||||||
msg:
|
: Center(),
|
||||||
'Home group is not supported',
|
Positioned(
|
||||||
gravity: ToastGravity.BOTTOM,
|
right: 30,
|
||||||
)
|
bottom: 30,
|
||||||
: showGeneralDialog(
|
child: _saveButton(context),
|
||||||
context: context,
|
),
|
||||||
barrierDismissible: true,
|
_showSetting
|
||||||
barrierLabel:
|
? Positioned(
|
||||||
MaterialLocalizations.of(
|
right: 30 * _menuValue,
|
||||||
context)
|
bottom: 100,
|
||||||
.modalBarrierDismissLabel,
|
child: Container(
|
||||||
barrierColor: Colors.black54,
|
alignment: Alignment.centerRight,
|
||||||
transitionDuration:
|
child: Column(
|
||||||
const Duration(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
milliseconds: 300),
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
pageBuilder: (BuildContext
|
children: <Widget>[
|
||||||
context,
|
Material(
|
||||||
Animation animaiton,
|
color: Colors.transparent,
|
||||||
Animation
|
child: InkWell(
|
||||||
secondaryAnimation) =>
|
onTap: () {
|
||||||
RenameGroup(
|
_menuController.reverse();
|
||||||
group: _groups[_index],
|
setState(() => _showSetting = false);
|
||||||
));
|
_index == 0
|
||||||
},
|
? Fluttertoast.showToast(
|
||||||
child: Container(
|
msg:
|
||||||
height: 30.0,
|
'Home group is not supported',
|
||||||
decoration: BoxDecoration(
|
gravity: ToastGravity.BOTTOM,
|
||||||
color: Colors.grey[700],
|
)
|
||||||
borderRadius: BorderRadius.all(
|
: showGeneralDialog(
|
||||||
Radius.circular(10.0))),
|
context: context,
|
||||||
padding: EdgeInsets.symmetric(
|
barrierDismissible: true,
|
||||||
horizontal: 10),
|
barrierLabel:
|
||||||
child: Row(
|
MaterialLocalizations.of(
|
||||||
children: <Widget>[
|
context)
|
||||||
Icon(
|
.modalBarrierDismissLabel,
|
||||||
Icons.text_fields,
|
barrierColor: Colors.black54,
|
||||||
color: Colors.white,
|
transitionDuration:
|
||||||
size: 15.0,
|
const Duration(
|
||||||
),
|
milliseconds: 300),
|
||||||
Padding(
|
pageBuilder: (BuildContext
|
||||||
padding: EdgeInsets.symmetric(
|
context,
|
||||||
horizontal: 5.0),
|
Animation animaiton,
|
||||||
),
|
Animation
|
||||||
Text('Edit Name',
|
secondaryAnimation) =>
|
||||||
style: TextStyle(
|
RenameGroup(
|
||||||
color: Colors.white)),
|
group: _groups[_index],
|
||||||
],
|
));
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 30.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[700],
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(10.0))),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Icon(
|
||||||
|
Icons.text_fields,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 15.0,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 5.0),
|
||||||
|
),
|
||||||
|
Text('Edit Name',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white)),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.symmetric(
|
||||||
padding:
|
vertical: 10.0)),
|
||||||
EdgeInsets.symmetric(vertical: 10.0)),
|
Material(
|
||||||
Material(
|
color: Colors.transparent,
|
||||||
color: Colors.transparent,
|
child: InkWell(
|
||||||
child: InkWell(
|
onTap: () {
|
||||||
onTap: () {
|
_menuController.reverse();
|
||||||
_menuController.reverse();
|
setState(() => _showSetting = false);
|
||||||
setState(() => _showSetting = false);
|
_index == 0
|
||||||
_index == 0
|
? Fluttertoast.showToast(
|
||||||
? Fluttertoast.showToast(
|
msg:
|
||||||
msg:
|
'Home group is not supported',
|
||||||
'Home group is not supported',
|
gravity: ToastGravity.BOTTOM,
|
||||||
gravity: ToastGravity.BOTTOM,
|
)
|
||||||
)
|
: showGeneralDialog(
|
||||||
: showGeneralDialog(
|
context: context,
|
||||||
context: context,
|
barrierDismissible: true,
|
||||||
barrierDismissible: true,
|
barrierLabel:
|
||||||
barrierLabel:
|
MaterialLocalizations.of(
|
||||||
MaterialLocalizations.of(
|
context)
|
||||||
context)
|
.modalBarrierDismissLabel,
|
||||||
.modalBarrierDismissLabel,
|
barrierColor: Colors.black54,
|
||||||
barrierColor: Colors.black54,
|
transitionDuration:
|
||||||
transitionDuration:
|
const Duration(
|
||||||
const Duration(
|
milliseconds: 300),
|
||||||
milliseconds: 300),
|
pageBuilder: (BuildContext
|
||||||
pageBuilder: (BuildContext
|
context,
|
||||||
context,
|
Animation animaiton,
|
||||||
Animation animaiton,
|
Animation
|
||||||
Animation
|
secondaryAnimation) =>
|
||||||
secondaryAnimation) =>
|
AnnotatedRegion<
|
||||||
AnnotatedRegion<
|
SystemUiOverlayStyle>(
|
||||||
SystemUiOverlayStyle>(
|
value:
|
||||||
value:
|
SystemUiOverlayStyle(
|
||||||
SystemUiOverlayStyle(
|
statusBarIconBrightness:
|
||||||
statusBarIconBrightness:
|
Brightness.light,
|
||||||
Brightness.light,
|
systemNavigationBarColor:
|
||||||
systemNavigationBarColor:
|
Theme.of(context)
|
||||||
Theme.of(context)
|
.brightness ==
|
||||||
.brightness ==
|
Brightness
|
||||||
Brightness
|
.light
|
||||||
.light
|
? Color
|
||||||
? Color
|
.fromRGBO(
|
||||||
.fromRGBO(
|
113,
|
||||||
113,
|
113,
|
||||||
113,
|
113,
|
||||||
113,
|
1)
|
||||||
1)
|
: Color
|
||||||
: Color
|
.fromRGBO(
|
||||||
.fromRGBO(
|
15,
|
||||||
15,
|
15,
|
||||||
15,
|
15,
|
||||||
15,
|
1),
|
||||||
1),
|
// statusBarColor: Theme.of(
|
||||||
// statusBarColor: Theme.of(
|
// context)
|
||||||
// context)
|
// .brightness ==
|
||||||
// .brightness ==
|
// Brightness.light
|
||||||
// Brightness.light
|
// ? Color.fromRGBO(
|
||||||
// ? Color.fromRGBO(
|
// 113,
|
||||||
// 113,
|
// 113,
|
||||||
// 113,
|
// 113,
|
||||||
// 113,
|
// 1)
|
||||||
// 1)
|
// : Color.fromRGBO(
|
||||||
// : Color.fromRGBO(
|
// 5, 5, 5, 1),
|
||||||
// 5, 5, 5, 1),
|
),
|
||||||
),
|
child: AlertDialog(
|
||||||
child: AlertDialog(
|
elevation: 1,
|
||||||
elevation: 1,
|
shape: RoundedRectangleBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius
|
||||||
borderRadius:
|
.all(Radius
|
||||||
BorderRadius.all(
|
.circular(
|
||||||
Radius.circular(
|
10.0))),
|
||||||
10.0))),
|
titlePadding:
|
||||||
titlePadding:
|
EdgeInsets.only(
|
||||||
EdgeInsets.only(
|
top: 20,
|
||||||
top: 20,
|
left: 20,
|
||||||
left: 20,
|
right: 200,
|
||||||
right: 200,
|
bottom: 20),
|
||||||
bottom: 20),
|
title: Text(
|
||||||
title: Text(
|
'Delete confirm'),
|
||||||
'Delete confirm'),
|
content: Text(
|
||||||
content: Text(
|
'Are you sure you want to delete this group? Podcasts will be moved to Home group.'),
|
||||||
'Are you sure you want to delete this group? Podcasts will be moved to Home group.'),
|
actions: <Widget>[
|
||||||
actions: <Widget>[
|
FlatButton(
|
||||||
FlatButton(
|
onPressed: () =>
|
||||||
onPressed: () =>
|
Navigator.of(
|
||||||
|
context)
|
||||||
|
.pop(),
|
||||||
|
child: Text(
|
||||||
|
'CANCEL',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors
|
||||||
|
.grey[
|
||||||
|
600]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FlatButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (_index ==
|
||||||
|
groupList
|
||||||
|
.groups
|
||||||
|
.length -
|
||||||
|
1) {
|
||||||
|
setState(() {
|
||||||
|
_index =
|
||||||
|
_index -
|
||||||
|
1;
|
||||||
|
_scroll = 0;
|
||||||
|
});
|
||||||
|
groupList.delGroup(
|
||||||
|
_groups[
|
||||||
|
_index +
|
||||||
|
1]);
|
||||||
|
} else {
|
||||||
|
groupList.delGroup(
|
||||||
|
_groups[
|
||||||
|
_index]);
|
||||||
|
}
|
||||||
Navigator.of(
|
Navigator.of(
|
||||||
context)
|
context)
|
||||||
.pop(),
|
.pop();
|
||||||
child: Text(
|
},
|
||||||
'CANCEL',
|
child: Text(
|
||||||
style: TextStyle(
|
'CONFIRM',
|
||||||
color: Colors
|
style: TextStyle(
|
||||||
.grey[
|
color: Colors
|
||||||
600]),
|
.red),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
FlatButton(
|
],
|
||||||
onPressed: () {
|
),
|
||||||
if (_index ==
|
));
|
||||||
groupList
|
},
|
||||||
.groups
|
child: Container(
|
||||||
.length -
|
height: 30,
|
||||||
1) {
|
decoration: BoxDecoration(
|
||||||
setState(() {
|
color: Colors.grey[700],
|
||||||
_index =
|
borderRadius: BorderRadius.all(
|
||||||
_index -
|
Radius.circular(10.0))),
|
||||||
1;
|
padding: EdgeInsets.symmetric(
|
||||||
_scroll = 0;
|
horizontal: 10),
|
||||||
});
|
child: Row(
|
||||||
groupList.delGroup(
|
children: <Widget>[
|
||||||
_groups[
|
Icon(
|
||||||
_index +
|
Icons.delete_outline,
|
||||||
1]);
|
color: Colors.red,
|
||||||
} else {
|
size: 15.0,
|
||||||
groupList.delGroup(
|
),
|
||||||
_groups[
|
Padding(
|
||||||
_index]);
|
padding: EdgeInsets.symmetric(
|
||||||
}
|
horizontal: 5.0),
|
||||||
Navigator.of(
|
),
|
||||||
context)
|
Text('Delete',
|
||||||
.pop();
|
style: TextStyle(
|
||||||
},
|
color: Colors.red)),
|
||||||
child: Text(
|
],
|
||||||
'CONFIRM',
|
),
|
||||||
style: TextStyle(
|
|
||||||
color: Colors
|
|
||||||
.red),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
height: 30,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.grey[700],
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(10.0))),
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 10),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Icon(
|
|
||||||
Icons.delete_outline,
|
|
||||||
color: Colors.red,
|
|
||||||
size: 15.0,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: 5.0),
|
|
||||||
),
|
|
||||||
Text('Delete',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.red)),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
)
|
: Center(),
|
||||||
: Center(),
|
],
|
||||||
],
|
);
|
||||||
);
|
}),
|
||||||
}),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
|||||||
|
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/util/ompl_build.dart';
|
import 'package:tsacdop/util/ompl_build.dart';
|
||||||
|
import 'package:tsacdop/util/context_extension.dart';
|
||||||
import 'theme.dart';
|
import 'theme.dart';
|
||||||
import 'storage.dart';
|
import 'storage.dart';
|
||||||
import 'history.dart';
|
import 'history.dart';
|
||||||
@ -56,7 +57,7 @@ class Settings extends StatelessWidget {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Settings'),
|
title: Text('Settings'),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: context.primaryColor,
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
@ -81,7 +82,7 @@ class Settings extends StatelessWidget {
|
|||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyText1
|
.bodyText1
|
||||||
.copyWith(color: Theme.of(context).accentColor)),
|
.copyWith(color: context.accentColor)),
|
||||||
),
|
),
|
||||||
ListView(
|
ListView(
|
||||||
physics: ClampingScrollPhysics(),
|
physics: ClampingScrollPhysics(),
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:app_settings/app_settings.dart';
|
import 'package:app_settings/app_settings.dart';
|
||||||
import 'package:tsacdop/settings/downloads_manage.dart';
|
import 'package:tsacdop/settings/downloads_manage.dart';
|
||||||
|
import 'package:tsacdop/class/settingstate.dart';
|
||||||
|
|
||||||
class StorageSetting extends StatelessWidget {
|
class StorageSetting extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var settings = Provider.of<SettingState>(context, listen: false);
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
statusBarIconBrightness: Theme.of(context).accentColorBrightness,
|
||||||
@ -24,6 +27,54 @@ class StorageSetting extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 30.0,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 80),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text('Network',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText1
|
||||||
|
.copyWith(color: Theme.of(context).accentColor)),
|
||||||
|
),
|
||||||
|
ListView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => DownloadsManage())),
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets.only(left: 80.0, right: 25),
|
||||||
|
title: Text('Ask before using cellular data'),
|
||||||
|
subtitle: Text(
|
||||||
|
'Ask to confirem when using cellular data to download episodes.'),
|
||||||
|
trailing: Selector<SettingState, bool>(
|
||||||
|
selector: (_, settings) =>
|
||||||
|
settings.downloadUsingData,
|
||||||
|
builder: (_, data, __) {
|
||||||
|
return Switch(
|
||||||
|
value: data,
|
||||||
|
onChanged: (value) =>
|
||||||
|
settings.downloadUsingData = value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(height: 2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]),
|
||||||
Column(
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -71,11 +71,10 @@ class SyncingSetting extends StatelessWidget {
|
|||||||
value: data.item1,
|
value: data.item1,
|
||||||
onChanged: (boo) async {
|
onChanged: (boo) async {
|
||||||
settings.autoUpdate = boo;
|
settings.autoUpdate = boo;
|
||||||
if (boo) {
|
if (boo)
|
||||||
settings.setWorkManager(data.item2);
|
settings.setWorkManager(data.item2);
|
||||||
} else {
|
else
|
||||||
settings.cancelWork();
|
settings.cancelWork();
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Divider(height: 2),
|
Divider(height: 2),
|
||||||
@ -92,7 +91,8 @@ class SyncingSetting extends StatelessWidget {
|
|||||||
elevation: 1,
|
elevation: 1,
|
||||||
value: data.item2,
|
value: data.item2,
|
||||||
onChanged: data.item1
|
onChanged: data.item1
|
||||||
? (value) {
|
? (value) async {
|
||||||
|
await settings.cancelWork();
|
||||||
settings.setWorkManager(value);
|
settings.setWorkManager(value);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:tsacdop/class/settingstate.dart';
|
import 'package:tsacdop/class/settingstate.dart';
|
||||||
|
import 'package:tsacdop/util/context_extension.dart';
|
||||||
|
|
||||||
class ThemeSetting extends StatelessWidget {
|
class ThemeSetting extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
@ -61,11 +62,11 @@ class ThemeSetting extends StatelessWidget {
|
|||||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarIconBrightness: Brightness.light,
|
statusBarIconBrightness: Brightness.light,
|
||||||
// systemNavigationBarColor:
|
systemNavigationBarColor:
|
||||||
// Theme.of(context).brightness ==
|
Theme.of(context).brightness ==
|
||||||
// Brightness.light
|
Brightness.light
|
||||||
// ? Color.fromRGBO(113, 113, 113, 1)
|
? Color.fromRGBO(113, 113, 113, 1)
|
||||||
// : Color.fromRGBO(15, 15, 15, 1),
|
: Color.fromRGBO(15, 15, 15, 1),
|
||||||
// statusBarColor:
|
// statusBarColor:
|
||||||
// Theme.of(context).brightness ==
|
// Theme.of(context).brightness ==
|
||||||
// Brightness.light
|
// Brightness.light
|
||||||
@ -138,11 +139,11 @@ class ThemeSetting extends StatelessWidget {
|
|||||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarIconBrightness: Brightness.light,
|
statusBarIconBrightness: Brightness.light,
|
||||||
// systemNavigationBarColor:
|
systemNavigationBarColor:
|
||||||
// Theme.of(context).brightness ==
|
Theme.of(context).brightness ==
|
||||||
// Brightness.light
|
Brightness.light
|
||||||
// ? Color.fromRGBO(113, 113, 113, 1)
|
? Color.fromRGBO(113, 113, 113, 1)
|
||||||
// : Color.fromRGBO(15, 15, 15, 1),
|
: Color.fromRGBO(15, 15, 15, 1),
|
||||||
// statusBarColor:
|
// statusBarColor:
|
||||||
// Theme.of(context).brightness ==
|
// Theme.of(context).brightness ==
|
||||||
// Brightness.light
|
// Brightness.light
|
||||||
@ -155,7 +156,7 @@ class ThemeSetting extends StatelessWidget {
|
|||||||
top: 20,
|
top: 20,
|
||||||
left: 40,
|
left: 40,
|
||||||
right: 200,
|
right: 200,
|
||||||
bottom: 20),
|
bottom: 0),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: BorderRadius.all(
|
||||||
Radius.circular(10.0))),
|
Radius.circular(10.0))),
|
||||||
@ -165,7 +166,7 @@ class ThemeSetting extends StatelessWidget {
|
|||||||
onColorChanged: (value) {
|
onColorChanged: (value) {
|
||||||
settings.setAccentColor = value;
|
settings.setAccentColor = value;
|
||||||
},
|
},
|
||||||
pickerColor: Colors.blue,
|
pickerColor: context.accentColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
|
11
lib/util/context_extension.dart
Normal file
11
lib/util/context_extension.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
extension ContextExtension on BuildContext{
|
||||||
|
Color get primaryColor => Theme.of(this).primaryColor;
|
||||||
|
Color get accentColor => Theme.of(this).accentColor;
|
||||||
|
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
|
||||||
|
Color get primaryColorDark => Theme.of(this).primaryColorDark;
|
||||||
|
Brightness get brightness => Theme.of(this).brightness;
|
||||||
|
double get width => MediaQuery.of(this).size.width;
|
||||||
|
double get height => MediaQuery.of(this).size.width;
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -21,18 +22,15 @@ class EpisodeGrid extends StatelessWidget {
|
|||||||
final bool showFavorite;
|
final bool showFavorite;
|
||||||
final bool showDownload;
|
final bool showDownload;
|
||||||
final bool showNumber;
|
final bool showNumber;
|
||||||
final int updateCount;
|
|
||||||
final String heroTag;
|
|
||||||
final int episodeCount;
|
final int episodeCount;
|
||||||
EpisodeGrid(
|
EpisodeGrid(
|
||||||
{Key key,
|
{Key key,
|
||||||
@required this.episodes,
|
@required this.episodes,
|
||||||
this.heroTag = '',
|
|
||||||
this.showDownload = false,
|
this.showDownload = false,
|
||||||
this.showFavorite = false,
|
this.showFavorite = false,
|
||||||
this.showNumber = false,
|
this.showNumber = false,
|
||||||
this.updateCount = 0,
|
this.episodeCount = 0
|
||||||
this.episodeCount = 0})
|
})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -183,7 +181,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
index < updateCount
|
episodes[index].isNew == 1
|
||||||
? Text('New',
|
? Text('New',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
@ -197,7 +195,7 @@ class EpisodeGrid extends StatelessWidget {
|
|||||||
? Container(
|
? Container(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: Text(
|
child: Text(
|
||||||
(episodeCount- index).toString(),
|
(episodeCount - index).toString(),
|
||||||
style: GoogleFonts.teko(
|
style: GoogleFonts.teko(
|
||||||
textStyle: TextStyle(
|
textStyle: TextStyle(
|
||||||
fontSize: _width / 24,
|
fontSize: _width / 24,
|
||||||
@ -240,10 +238,6 @@ class EpisodeGrid extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
showDownload
|
|
||||||
? DownloadIcon(
|
|
||||||
episodeBrief: episodes[index])
|
|
||||||
: Center(),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.all(1),
|
padding: EdgeInsets.all(1),
|
||||||
),
|
),
|
||||||
@ -280,148 +274,6 @@ class EpisodeGrid extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadIcon extends StatefulWidget {
|
|
||||||
final EpisodeBrief episodeBrief;
|
|
||||||
DownloadIcon({this.episodeBrief, Key key}) : super(key: key);
|
|
||||||
@override
|
|
||||||
_DownloadIconState createState() => _DownloadIconState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DownloadIconState extends State<DownloadIcon> {
|
|
||||||
_TaskInfo _task;
|
|
||||||
bool _isLoading;
|
|
||||||
ReceivePort _port = ReceivePort();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_bindBackgroundIsolate();
|
|
||||||
|
|
||||||
FlutterDownloader.registerCallback(downloadCallback);
|
|
||||||
|
|
||||||
_isLoading = true;
|
|
||||||
_prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_unbindBackgroundIsolate();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _bindBackgroundIsolate() {
|
|
||||||
bool isSuccess = IsolateNameServer.registerPortWithName(
|
|
||||||
_port.sendPort, 'downloader_send_port');
|
|
||||||
if (!isSuccess) {
|
|
||||||
_unbindBackgroundIsolate();
|
|
||||||
_bindBackgroundIsolate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_port.listen((dynamic data) {
|
|
||||||
print('UI isolate callback: $data');
|
|
||||||
String id = data[0];
|
|
||||||
DownloadTaskStatus status = data[1];
|
|
||||||
int progress = data[2];
|
|
||||||
if (_task.taskId == id) {
|
|
||||||
setState(() {
|
|
||||||
_task.status = status;
|
|
||||||
_task.progress = progress;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unbindBackgroundIsolate() {
|
|
||||||
IsolateNameServer.removePortNameMapping('downloader_send_port');
|
|
||||||
}
|
|
||||||
|
|
||||||
static void downloadCallback(
|
|
||||||
String id, DownloadTaskStatus status, int progress) {
|
|
||||||
print('Background callback task in $id status ($status) $progress');
|
|
||||||
final SendPort send =
|
|
||||||
IsolateNameServer.lookupPortByName('downloader_send_port');
|
|
||||||
send.send([id, status, progress]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Null> _prepare() async {
|
|
||||||
final tasks = await FlutterDownloader.loadTasks();
|
|
||||||
|
|
||||||
_task = _TaskInfo(
|
|
||||||
name: widget.episodeBrief.title,
|
|
||||||
link: widget.episodeBrief.enclosureUrl);
|
|
||||||
|
|
||||||
tasks?.forEach((task) {
|
|
||||||
if (_task.link == task.url) {
|
|
||||||
_task.taskId = task.taskId;
|
|
||||||
_task.status = task.status;
|
|
||||||
_task.progress = task.progress;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return _downloadButton(_task);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _downloadButton(_TaskInfo task) {
|
|
||||||
if (_isLoading)
|
|
||||||
return Center();
|
|
||||||
else if (task.status == DownloadTaskStatus.running) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 12,
|
|
||||||
width: 12,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
backgroundColor: Colors.grey[200],
|
|
||||||
strokeWidth: 1,
|
|
||||||
valueColor:
|
|
||||||
AlwaysStoppedAnimation<Color>(Theme.of(context).accentColor),
|
|
||||||
value: task.progress / 100,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (task.status == DownloadTaskStatus.paused) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 12,
|
|
||||||
width: 12,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
backgroundColor: Colors.grey[200],
|
|
||||||
strokeWidth: 1,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
|
|
||||||
value: task.progress / 100,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (task.status == DownloadTaskStatus.complete) {
|
|
||||||
return IconTheme(
|
|
||||||
data: IconThemeData(size: 15),
|
|
||||||
child: Icon(
|
|
||||||
Icons.done_all,
|
|
||||||
color: Theme.of(context).accentColor,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (task.status == DownloadTaskStatus.failed) {
|
|
||||||
return IconTheme(
|
|
||||||
data: IconThemeData(size: 15),
|
|
||||||
child: Icon(Icons.refresh, color: Colors.red),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Center();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TaskInfo {
|
|
||||||
final String name;
|
|
||||||
final String link;
|
|
||||||
|
|
||||||
String taskId;
|
|
||||||
int progress = 0;
|
|
||||||
DownloadTaskStatus status = DownloadTaskStatus.undefined;
|
|
||||||
|
|
||||||
_TaskInfo({this.name, this.link});
|
|
||||||
}
|
|
||||||
|
|
||||||
class OpenContainerWrapper extends StatelessWidget {
|
class OpenContainerWrapper extends StatelessWidget {
|
||||||
const OpenContainerWrapper({
|
const OpenContainerWrapper({
|
||||||
@ -469,3 +321,5 @@ class OpenContainerWrapper extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,13 +53,18 @@ dev_dependencies:
|
|||||||
app_settings: ^3.0.1
|
app_settings: ^3.0.1
|
||||||
fl_chart: ^0.8.7
|
fl_chart: ^0.8.7
|
||||||
audio_service: ^0.6.2
|
audio_service: ^0.6.2
|
||||||
just_audio: ^0.1.3
|
just_audio:
|
||||||
|
git:
|
||||||
|
url: https://github.com/stonega/just_audio.git
|
||||||
rxdart: ^0.23.1
|
rxdart: ^0.23.1
|
||||||
line_icons:
|
line_icons:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/galonsos/line_icons.git
|
url: https://github.com/galonsos/line_icons.git
|
||||||
flutter_file_dialog: ^0.0.5
|
flutter_file_dialog: ^0.0.5
|
||||||
flutter_linkify: ^3.1.0
|
flutter_linkify: ^3.1.0
|
||||||
|
extended_nested_scroll_view: ^0.4.0
|
||||||
|
connectivity: ^0.4.8+2
|
||||||
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://www.dartlang.org/tools/pub/pubspec
|
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||||
|
Loading…
x
Reference in New Issue
Block a user