mirror of
https://github.com/stonega/tsacdop
synced 2025-03-10 08:10:09 +01:00
modified: lib/class/audiostate.dart
modified: lib/class/settingstate.dart modified: lib/episodes/episodedetail.dart modified: lib/home/appbar/addpodcast.dart deleted: lib/home/audio_player.dart modified: lib/home/audiopanel.dart modified: lib/home/home.dart modified: lib/home/homescroll.dart modified: lib/local_storage/key_value_storage.dart modified: lib/local_storage/sqflite_localpodcast.dart modified: lib/main.dart modified: lib/podcasts/podcastdetail.dart modified: lib/podcasts/podcastgroup.dart modified: pubspec.lock modified: pubspec.yaml lib/home/audioplayer.dart
This commit is contained in:
parent
d4ebdf769d
commit
547aef80e9
@ -1,21 +1,432 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
|
import 'package:audiofileplayer/audiofileplayer.dart';
|
||||||
|
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:audiofileplayer/audio_system.dart';
|
||||||
|
import 'package:tsacdop/local_storage/key_value_storage.dart';
|
||||||
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
|
|
||||||
enum AudioState {load, play, pause, complete, error}
|
enum AudioState { load, play, pause, complete, error, stop }
|
||||||
|
|
||||||
class AudioPlay with ChangeNotifier {
|
class PlayHistory {
|
||||||
|
String title;
|
||||||
|
String url;
|
||||||
|
double seconds;
|
||||||
|
double seekValue;
|
||||||
|
PlayHistory(this.title, this.url, this.seconds, this.seekValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Playlist {
|
||||||
|
String name;
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
|
List<String> urls;
|
||||||
|
List<EpisodeBrief> _playlist;
|
||||||
|
List<EpisodeBrief> get playlist => _playlist;
|
||||||
|
KeyValueStorage storage = KeyValueStorage('playlist');
|
||||||
|
Playlist(this.name, {List<String> urls}) : urls = urls ?? [];
|
||||||
|
|
||||||
|
getPlaylist() async{
|
||||||
|
List<String> _urls = await storage.getPlayList();
|
||||||
|
if (_urls.length == 0) {
|
||||||
|
_playlist = [];
|
||||||
|
} else {
|
||||||
|
_playlist = [];
|
||||||
|
await Future.forEach(_urls, (url) async{
|
||||||
|
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
|
||||||
|
print(episode.title);
|
||||||
|
_playlist.add(episode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print(_playlist.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
savePlaylist() async {
|
||||||
|
urls = [];
|
||||||
|
urls.addAll(_playlist.map((e) => e.enclosureUrl));
|
||||||
|
print(urls);
|
||||||
|
await storage.savePlaylist(urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPlayList(EpisodeBrief episodeBrief) async {
|
||||||
|
_playlist.add(episodeBrief);
|
||||||
|
await savePlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
delFromPlaylist(EpisodeBrief episodeBrief) {
|
||||||
|
_playlist.remove(episodeBrief);
|
||||||
|
savePlaylist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AudioPlayer extends ChangeNotifier {
|
||||||
|
static const String replay10ButtonId = 'replay10ButtonId';
|
||||||
|
static const String newReleasesButtonId = 'newReleasesButtonId';
|
||||||
|
static const String likeButtonId = 'likeButtonId';
|
||||||
|
static const String pausenowButtonId = 'pausenowButtonId';
|
||||||
|
static const String forwardButtonId = 'forwardButtonId';
|
||||||
|
|
||||||
|
DBHelper dbHelper = DBHelper();
|
||||||
EpisodeBrief _episode;
|
EpisodeBrief _episode;
|
||||||
EpisodeBrief get episode => _episode;
|
Playlist _queue = Playlist('now');
|
||||||
set episodeLoad(EpisodeBrief episdoe){
|
bool _playerRunning = false;
|
||||||
_episode = episdoe;
|
Audio _backgroundAudio;
|
||||||
notifyListeners();
|
bool _backgroundAudioPlaying = false;
|
||||||
}
|
double _backgroundAudioDurationSeconds = 0;
|
||||||
|
double _backgroundAudioPositionSeconds = 0;
|
||||||
AudioState _audioState;
|
bool _remoteAudioLoading = false;
|
||||||
|
String _remoteErrorMessage;
|
||||||
|
double _seekSliderValue = 0.0;
|
||||||
|
final Logger _logger = Logger('audiofileplayer');
|
||||||
|
|
||||||
|
bool get backgroundAudioPlaying => _backgroundAudioPlaying;
|
||||||
|
bool get remoteAudioLoading => _remoteAudioLoading;
|
||||||
|
double get backgroundAudioDuration => _backgroundAudioDurationSeconds;
|
||||||
|
double get backgroundAudioPosition => _backgroundAudioPositionSeconds;
|
||||||
|
double get seekSliderValue => _seekSliderValue;
|
||||||
|
String get remoteErrorMessage => _remoteErrorMessage;
|
||||||
|
bool get playerRunning => _playerRunning;
|
||||||
|
|
||||||
|
Playlist get queue => _queue;
|
||||||
|
|
||||||
|
AudioState _audioState = AudioState.stop;
|
||||||
AudioState get audioState => _audioState;
|
AudioState get audioState => _audioState;
|
||||||
set audioState(AudioState state){
|
|
||||||
_audioState = state;
|
EpisodeBrief get episode => _episode;
|
||||||
|
|
||||||
|
episodeLoad(EpisodeBrief episode) async {
|
||||||
|
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
|
||||||
|
_episode = episode;
|
||||||
|
await _queue.getPlaylist();
|
||||||
|
if (_queue.playlist.contains(_episode)) {
|
||||||
|
_queue.playlist.remove(_episode);
|
||||||
|
_queue.playlist.insert(0, _episode);
|
||||||
|
} else {
|
||||||
|
_queue.playlist.insert(0, _episode);
|
||||||
|
}
|
||||||
|
await _queue.savePlaylist();
|
||||||
|
await _play(_episode);
|
||||||
|
_playerRunning = true;
|
||||||
|
_backgroundAudioPlaying = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
playNext() async {
|
||||||
|
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||||
|
backgroundAudioDuration, seekSliderValue);
|
||||||
|
await dbHelper.saveHistory(history);
|
||||||
|
await _queue.delFromPlaylist(_episode);
|
||||||
|
if (_queue.playlist.length > 0) {
|
||||||
|
_episode = _queue.playlist.first;
|
||||||
|
_play(_episode);
|
||||||
|
_backgroundAudioPlaying = true;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
_backgroundAudioPlaying = false;
|
||||||
|
_remoteAudioLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPlaylist(EpisodeBrief episode) async {
|
||||||
|
_queue.addToPlayList(episode);
|
||||||
|
await _queue.getPlaylist();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseAduio() async {
|
||||||
|
_pauseBackgroundAudio();
|
||||||
|
_audioState = AudioState.pause;
|
||||||
|
notifyListeners();
|
||||||
|
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
|
||||||
|
backgroundAudioDuration, seekSliderValue);
|
||||||
|
await dbHelper.saveHistory(history);
|
||||||
|
await _queue.delFromPlaylist(_episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeAudio() {
|
||||||
|
_resumeBackgroundAudio();
|
||||||
|
_audioState = AudioState.play;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardAudio(double s) {
|
||||||
|
_forwardBackgroundAudio(s);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
sliderSeek(double val) {
|
||||||
|
_seekSliderValue = val;
|
||||||
|
notifyListeners();
|
||||||
|
final double positionSeconds = val * _backgroundAudioDurationSeconds;
|
||||||
|
_backgroundAudio.seek(positionSeconds);
|
||||||
|
AudioSystem.instance.setPlaybackState(true, positionSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
disopse() {
|
||||||
|
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
|
||||||
|
_backgroundAudio?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_play(EpisodeBrief episodeBrief) async {
|
||||||
|
String url = _queue.playlist.first.enclosureUrl;
|
||||||
|
_getFile(url).then((result) {
|
||||||
|
result == 'NotDownload'
|
||||||
|
? _initbackgroundAudioPlayer(url)
|
||||||
|
: _initbackgroundAudioPlayerLocal(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _getFile(String url) async {
|
||||||
|
final task = await FlutterDownloader.loadTasksWithRawQuery(
|
||||||
|
query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
|
||||||
|
if (task.length != 0) {
|
||||||
|
String _filePath = task.first.savedDir + '/' + task.first.filename;
|
||||||
|
return _filePath;
|
||||||
|
}
|
||||||
|
return 'NotDownload';
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteData _getAudio(String path) {
|
||||||
|
File audioFile = File(path);
|
||||||
|
Uint8List audio = audioFile.readAsBytesSync();
|
||||||
|
return ByteData.view(audio.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initbackgroundAudioPlayerLocal(String path) {
|
||||||
|
_remoteErrorMessage = null;
|
||||||
|
_remoteAudioLoading = true;
|
||||||
|
ByteData audio = _getAudio(path);
|
||||||
|
if (_backgroundAudioPlaying == true) {
|
||||||
|
_stopBackgroundAudio();
|
||||||
|
}
|
||||||
|
_backgroundAudioPositionSeconds = 0;
|
||||||
|
_setNotification(false);
|
||||||
|
_backgroundAudio =
|
||||||
|
Audio.loadFromByteData(audio, onDuration: (double durationSeconds) {
|
||||||
|
_backgroundAudioDurationSeconds = durationSeconds;
|
||||||
|
_remoteAudioLoading = false;
|
||||||
|
_backgroundAudioPlaying = true;
|
||||||
|
_setNotification(true);
|
||||||
|
notifyListeners();
|
||||||
|
}, onPosition: (double positionSeconds) {
|
||||||
|
if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
|
||||||
|
_seekSliderValue =
|
||||||
|
_backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
|
||||||
|
_backgroundAudioPositionSeconds = positionSeconds;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
_seekSliderValue = 1;
|
||||||
|
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}, onError: (String message) {
|
||||||
|
_remoteErrorMessage = message;
|
||||||
|
_backgroundAudio.dispose();
|
||||||
|
_backgroundAudio = null;
|
||||||
|
_backgroundAudioPlaying = false;
|
||||||
|
_remoteAudioLoading = false;
|
||||||
|
}, onComplete: () {
|
||||||
|
playNext();
|
||||||
|
}, looping: false, playInBackground: true)
|
||||||
|
..play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initbackgroundAudioPlayer(String url) {
|
||||||
|
_remoteErrorMessage = null;
|
||||||
|
_remoteAudioLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
if (_backgroundAudioPlaying == true) {
|
||||||
|
_stopBackgroundAudio();
|
||||||
|
}
|
||||||
|
_backgroundAudioPositionSeconds = 0;
|
||||||
|
_setNotification(false);
|
||||||
|
_backgroundAudio =
|
||||||
|
Audio.loadFromRemoteUrl(url, onDuration: (double durationSeconds) {
|
||||||
|
_backgroundAudioDurationSeconds = durationSeconds;
|
||||||
|
_remoteAudioLoading = false;
|
||||||
|
_backgroundAudioPlaying = true;
|
||||||
|
_setNotification(true);
|
||||||
|
notifyListeners();
|
||||||
|
}, onPosition: (double positionSeconds) {
|
||||||
|
if (_backgroundAudioPositionSeconds < _backgroundAudioDurationSeconds) {
|
||||||
|
_seekSliderValue =
|
||||||
|
_backgroundAudioPositionSeconds / _backgroundAudioDurationSeconds;
|
||||||
|
_backgroundAudioPositionSeconds = positionSeconds;
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
_seekSliderValue = 1;
|
||||||
|
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}, onError: (String message) {
|
||||||
|
_remoteErrorMessage = message;
|
||||||
|
_backgroundAudio.dispose();
|
||||||
|
_backgroundAudio = null;
|
||||||
|
_backgroundAudioPlaying = false;
|
||||||
|
_remoteAudioLoading = false;
|
||||||
|
}, onComplete: () {
|
||||||
|
playNext();
|
||||||
|
}, looping: false, playInBackground: true)
|
||||||
|
..resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _mediaEventListener(MediaEvent mediaEvent) {
|
||||||
|
_logger.info('App received media event of type: ${mediaEvent.type}');
|
||||||
|
final MediaActionType type = mediaEvent.type;
|
||||||
|
if (type == MediaActionType.play) {
|
||||||
|
_resumeBackgroundAudio();
|
||||||
|
} else if (type == MediaActionType.pause) {
|
||||||
|
_pauseBackgroundAudio();
|
||||||
|
} else if (type == MediaActionType.playPause) {
|
||||||
|
_backgroundAudioPlaying
|
||||||
|
? _pauseBackgroundAudio()
|
||||||
|
: _resumeBackgroundAudio();
|
||||||
|
} else if (type == MediaActionType.stop) {
|
||||||
|
_stopBackgroundAudio();
|
||||||
|
} else if (type == MediaActionType.seekTo) {
|
||||||
|
_backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
|
||||||
|
AudioSystem.instance
|
||||||
|
.setPlaybackState(true, mediaEvent.seekToPositionSeconds);
|
||||||
|
} else if (type == MediaActionType.skipForward) {
|
||||||
|
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
|
||||||
|
_forwardBackgroundAudio(skipIntervalSeconds);
|
||||||
|
_logger.info(
|
||||||
|
'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
|
||||||
|
} else if (type == MediaActionType.skipBackward) {
|
||||||
|
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
|
||||||
|
_forwardBackgroundAudio(skipIntervalSeconds);
|
||||||
|
_logger.info(
|
||||||
|
'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
|
||||||
|
} else if (type == MediaActionType.custom) {
|
||||||
|
if (mediaEvent.customEventId == replay10ButtonId) {
|
||||||
|
_forwardBackgroundAudio(-10.0);
|
||||||
|
} else if (mediaEvent.customEventId == likeButtonId) {
|
||||||
|
_resumeBackgroundAudio();
|
||||||
|
} else if (mediaEvent.customEventId == forwardButtonId) {
|
||||||
|
_forwardBackgroundAudio(30.0);
|
||||||
|
} else if (mediaEvent.customEventId == pausenowButtonId) {
|
||||||
|
_pauseBackgroundAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setNotification(bool b) async {
|
||||||
|
final Uint8List imageBytes =
|
||||||
|
File('${_episode.imagePath}').readAsBytesSync();
|
||||||
|
AudioSystem.instance.setMetadata(AudioMetadata(
|
||||||
|
title: episode.title,
|
||||||
|
artist: episode.feedTitle,
|
||||||
|
album: episode.feedTitle,
|
||||||
|
genre: "Podcast",
|
||||||
|
durationSeconds: _backgroundAudioDurationSeconds,
|
||||||
|
artBytes: imageBytes));
|
||||||
|
AudioSystem.instance.setPlaybackState(b, _backgroundAudioPositionSeconds);
|
||||||
|
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
||||||
|
AndroidMediaButtonType.pause,
|
||||||
|
_forwardButton,
|
||||||
|
AndroidMediaButtonType.stop,
|
||||||
|
], androidCompactIndices: <int>[
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]);
|
||||||
|
|
||||||
|
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
||||||
|
MediaActionType.playPause,
|
||||||
|
MediaActionType.pause,
|
||||||
|
MediaActionType.next,
|
||||||
|
MediaActionType.previous,
|
||||||
|
MediaActionType.skipForward,
|
||||||
|
MediaActionType.skipBackward,
|
||||||
|
MediaActionType.seekTo,
|
||||||
|
MediaActionType.custom,
|
||||||
|
}, skipIntervalSeconds: 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _resumeBackgroundAudio() async {
|
||||||
|
_backgroundAudio.resume();
|
||||||
|
|
||||||
|
_backgroundAudioPlaying = true;
|
||||||
|
|
||||||
|
final Uint8List imageBytes =
|
||||||
|
File('${_episode.imagePath}').readAsBytesSync();
|
||||||
|
AudioSystem.instance.setMetadata(AudioMetadata(
|
||||||
|
title: _episode.title,
|
||||||
|
artist: _episode.feedTitle,
|
||||||
|
album: _episode.feedTitle,
|
||||||
|
genre: "Podcast",
|
||||||
|
durationSeconds: _backgroundAudioDurationSeconds,
|
||||||
|
artBytes: imageBytes));
|
||||||
|
|
||||||
|
AudioSystem.instance
|
||||||
|
.setPlaybackState(true, _backgroundAudioPositionSeconds);
|
||||||
|
|
||||||
|
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
||||||
|
AndroidMediaButtonType.pause,
|
||||||
|
_forwardButton,
|
||||||
|
AndroidMediaButtonType.stop,
|
||||||
|
], androidCompactIndices: <int>[
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
]);
|
||||||
|
|
||||||
|
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
||||||
|
MediaActionType.playPause,
|
||||||
|
MediaActionType.pause,
|
||||||
|
MediaActionType.next,
|
||||||
|
MediaActionType.previous,
|
||||||
|
MediaActionType.skipForward,
|
||||||
|
MediaActionType.skipBackward,
|
||||||
|
MediaActionType.seekTo,
|
||||||
|
MediaActionType.custom,
|
||||||
|
}, skipIntervalSeconds: 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pauseBackgroundAudio() {
|
||||||
|
_backgroundAudio.pause();
|
||||||
|
_backgroundAudioPlaying = false;
|
||||||
|
AudioSystem.instance
|
||||||
|
.setPlaybackState(false, _backgroundAudioPositionSeconds);
|
||||||
|
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
||||||
|
AndroidMediaButtonType.play,
|
||||||
|
_forwardButton,
|
||||||
|
AndroidMediaButtonType.stop,
|
||||||
|
], androidCompactIndices: <int>[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
||||||
|
MediaActionType.playPause,
|
||||||
|
MediaActionType.play,
|
||||||
|
MediaActionType.next,
|
||||||
|
MediaActionType.previous,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopBackgroundAudio() {
|
||||||
|
_backgroundAudio..pause();
|
||||||
|
_backgroundAudio..dispose();
|
||||||
|
_backgroundAudioPlaying = false;
|
||||||
|
AudioSystem.instance.stopBackgroundDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _forwardBackgroundAudio(double seconds) {
|
||||||
|
final double forwardposition = _backgroundAudioPositionSeconds + seconds;
|
||||||
|
_backgroundAudio.seek(forwardposition);
|
||||||
|
//AudioSystem.instance.setPlaybackState(true, _backgroundAudioDurationSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _pauseButton = AndroidCustomMediaButton(
|
||||||
|
'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
|
||||||
|
final _replay10Button = AndroidCustomMediaButton(
|
||||||
|
'replay10', replay10ButtonId, 'ic_stat_replay_10');
|
||||||
|
final _forwardButton = AndroidCustomMediaButton(
|
||||||
|
'forward', forwardButtonId, 'ic_stat_forward_30');
|
||||||
|
final _playnowButton = AndroidCustomMediaButton(
|
||||||
|
'playnow', likeButtonId, 'ic_stat_play_circle_filled');
|
||||||
|
}
|
||||||
|
@ -8,10 +8,10 @@ class SettingState extends ChangeNotifier {
|
|||||||
int _theme;
|
int _theme;
|
||||||
|
|
||||||
int get theme => _theme;
|
int get theme => _theme;
|
||||||
void setTheme(int theme) {
|
setTheme(int theme) async{
|
||||||
_theme = theme;
|
_theme = theme;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
_saveTheme(theme);
|
await _saveTheme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -5,9 +5,11 @@ 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/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';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:tuple/tuple.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/sqflite_localpodcast.dart';
|
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||||
@ -66,116 +68,137 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||||||
title: Text(widget.episodeItem.feedTitle),
|
title: Text(widget.episodeItem.feedTitle),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
),
|
),
|
||||||
body: Container(
|
body: Stack(
|
||||||
color: Theme.of(context).primaryColor,
|
children: <Widget>[
|
||||||
padding: EdgeInsets.all(10.0),
|
Container(
|
||||||
child: Column(
|
color: Theme.of(context).primaryColor,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
padding: EdgeInsets.all(10.0),
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
children: <Widget>[
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
Container(
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: <Widget>[
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
Container(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
children: <Widget>[
|
||||||
alignment: Alignment.topLeft,
|
Container(
|
||||||
child: Text(
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
widget.episodeItem.title,
|
alignment: Alignment.topLeft,
|
||||||
style: Theme.of(context).textTheme.headline5,
|
child: Text(
|
||||||
),
|
widget.episodeItem.title,
|
||||||
),
|
style: Theme.of(context).textTheme.headline5,
|
||||||
Container(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
|
||||||
height: 30.0,
|
|
||||||
child: Text(
|
|
||||||
'Published ' +
|
|
||||||
DateFormat.yMMMd().format(
|
|
||||||
DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
widget.episodeItem.pubDate)),
|
|
||||||
style: TextStyle(color: Colors.blue[500])),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.all(12.0),
|
|
||||||
height: 50.0,
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
(widget.episodeItem.explicit == 1)
|
|
||||||
? Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red[800],
|
|
||||||
shape: BoxShape.circle),
|
|
||||||
height: 25.0,
|
|
||||||
width: 25.0,
|
|
||||||
margin: EdgeInsets.only(right: 10.0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text('E',
|
|
||||||
style: TextStyle(color: Colors.white)))
|
|
||||||
: Center(),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.cyan[300],
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(Radius.circular(15.0))),
|
|
||||||
height: 30.0,
|
|
||||||
margin: EdgeInsets.only(right: 10.0),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
(widget.episodeItem.duration).toString() +
|
|
||||||
'mins',
|
|
||||||
style: textstyle),
|
|
||||||
),
|
),
|
||||||
Container(
|
),
|
||||||
decoration: BoxDecoration(
|
Container(
|
||||||
color: Colors.lightBlue[300],
|
alignment: Alignment.centerLeft,
|
||||||
borderRadius:
|
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
BorderRadius.all(Radius.circular(15.0))),
|
height: 30.0,
|
||||||
height: 30.0,
|
child: Text(
|
||||||
margin: EdgeInsets.only(right: 10.0),
|
'Published ' +
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
DateFormat.yMMMd().format(
|
||||||
alignment: Alignment.center,
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
child: Text(
|
widget.episodeItem.pubDate)),
|
||||||
((widget.episodeItem.enclosureLength) ~/
|
style: TextStyle(color: Colors.blue[500])),
|
||||||
1000000)
|
),
|
||||||
.toString() +
|
Container(
|
||||||
'MB',
|
padding: EdgeInsets.all(12.0),
|
||||||
style: textstyle),
|
height: 50.0,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
(widget.episodeItem.explicit == 1)
|
||||||
|
? Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red[800],
|
||||||
|
shape: BoxShape.circle),
|
||||||
|
height: 25.0,
|
||||||
|
width: 25.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text('E',
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.white)))
|
||||||
|
: Center(),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.cyan[300],
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(15.0))),
|
||||||
|
height: 30.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
(widget.episodeItem.duration).toString() +
|
||||||
|
'mins',
|
||||||
|
style: textstyle),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.lightBlue[300],
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(15.0))),
|
||||||
|
height: 30.0,
|
||||||
|
margin: EdgeInsets.only(right: 10.0),
|
||||||
|
padding:
|
||||||
|
EdgeInsets.symmetric(horizontal: 10.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
((widget.episodeItem.enclosureLength) ~/
|
||||||
|
1000000)
|
||||||
|
.toString() +
|
||||||
|
'MB',
|
||||||
|
style: textstyle),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
child: _loaddes
|
|
||||||
? (widget.episodeItem.description.contains('<'))
|
|
||||||
? Html(
|
|
||||||
data: widget.episodeItem.description,
|
|
||||||
onLinkTap: (url) {
|
|
||||||
_launchUrl(url);
|
|
||||||
},
|
|
||||||
useRichText: true,
|
|
||||||
)
|
|
||||||
: Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
child: Text(widget.episodeItem.description))
|
|
||||||
: Center(),
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: _loaddes
|
||||||
|
? (widget.episodeItem.description.contains('<'))
|
||||||
|
? Html(
|
||||||
|
data: widget.episodeItem.description,
|
||||||
|
onLinkTap: (url) {
|
||||||
|
_launchUrl(url);
|
||||||
|
},
|
||||||
|
useRichText: true,
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child:
|
||||||
|
Text(widget.episodeItem.description))
|
||||||
|
: Center(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
MenuBar(
|
),
|
||||||
episodeItem: widget.episodeItem,
|
Selector<AudioPlayer, bool>(
|
||||||
heroTag: widget.heroTag,
|
selector: (_, audio) => audio.playerRunning,
|
||||||
),
|
builder: (_, data, __) {
|
||||||
],
|
return Container(
|
||||||
),
|
alignment: Alignment.bottomCenter,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 5.0,
|
||||||
|
right: 5.0,
|
||||||
|
bottom: data == true ? 80.0 : 10.0),
|
||||||
|
child: MenuBar(
|
||||||
|
episodeItem: widget.episodeItem,
|
||||||
|
heroTag: widget.heroTag,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Container(child: PlayerWidget()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -233,39 +256,37 @@ class _MenuBarState extends State<MenuBar> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final audioplay = Provider.of<AudioPlay>(context);
|
var audio = Provider.of<AudioPlayer>(context, listen: false);
|
||||||
return Container(
|
return Container(
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? Colors.grey[200]
|
||||||
|
: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
(widget.episodeItem.title == audioplay.episode?.title &&
|
Hero(
|
||||||
audioplay.audioState == AudioState.play)
|
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
|
||||||
? ImageRotate(
|
child: Container(
|
||||||
title: widget.episodeItem.feedTitle,
|
padding: EdgeInsets.symmetric(horizontal:10.0),
|
||||||
path: widget.episodeItem.imagePath,
|
child: ClipRRect(
|
||||||
)
|
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||||
: Hero(
|
child: Container(
|
||||||
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
|
height: 30.0,
|
||||||
child: Container(
|
width: 30.0,
|
||||||
padding: EdgeInsets.all(10.0),
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
child: ClipRRect(
|
child: Image.file(File("${widget.episodeItem.imagePath}")),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
|
||||||
child: Container(
|
|
||||||
height: 30.0,
|
|
||||||
width: 30.0,
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
child:
|
|
||||||
Image.file(File("${widget.episodeItem.imagePath}")),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
(_like == 0 && !_liked)
|
(_like == 0 && !_liked)
|
||||||
? _buttonOnMenu(
|
? _buttonOnMenu(
|
||||||
Icon(
|
Icon(
|
||||||
@ -273,72 +294,86 @@ class _MenuBarState extends State<MenuBar> {
|
|||||||
color: Colors.grey[700],
|
color: Colors.grey[700],
|
||||||
),
|
),
|
||||||
() => saveLiked(widget.episodeItem.enclosureUrl))
|
() => saveLiked(widget.episodeItem.enclosureUrl))
|
||||||
: Stack(
|
: (_like == 1 && !_liked)
|
||||||
alignment: Alignment.center,
|
? _buttonOnMenu(
|
||||||
children: <Widget>[
|
Icon(
|
||||||
LoveOpen(),
|
Icons.favorite,
|
||||||
_buttonOnMenu(
|
color: Colors.red,
|
||||||
Icon(
|
),
|
||||||
Icons.favorite,
|
() => setUnliked(widget.episodeItem.enclosureUrl))
|
||||||
color: Colors.red,
|
: Stack(
|
||||||
),
|
alignment: Alignment.center,
|
||||||
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
children: <Widget>[
|
||||||
],
|
LoveOpen(),
|
||||||
),
|
_buttonOnMenu(
|
||||||
|
Icon(
|
||||||
|
Icons.favorite,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
||||||
|
],
|
||||||
|
),
|
||||||
DownloadButton(episodeBrief: widget.episodeItem),
|
DownloadButton(episodeBrief: widget.episodeItem),
|
||||||
_buttonOnMenu(Icon(Icons.playlist_add, color: Colors.grey[700]), () {
|
_buttonOnMenu(Icon(Icons.playlist_add, color: Colors.grey[700]), () {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: 'Not support yet',
|
msg: 'Added to playlist',
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
);
|
);
|
||||||
/*TODO*/
|
audio.addToPlaylist(widget.episodeItem);
|
||||||
}),
|
}),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
(widget.episodeItem.title != audioplay.episode?.title)
|
// Text(audio.audioState.toString()),
|
||||||
? Material(
|
Selector<AudioPlayer, Tuple2<EpisodeBrief, bool>>(
|
||||||
color: Colors.transparent,
|
selector: (_, audio) =>
|
||||||
child: InkWell(
|
Tuple2(audio.episode, audio.backgroundAudioPlaying),
|
||||||
borderRadius: BorderRadius.only(
|
builder: (_, data, __) {
|
||||||
topRight: Radius.circular(5.0),
|
return (widget.episodeItem.title != data.item1?.title)
|
||||||
bottomRight: Radius.circular(5.0)),
|
? Material(
|
||||||
onTap: () {
|
color: Colors.transparent,
|
||||||
audioplay.episodeLoad = widget.episodeItem;
|
child: InkWell(
|
||||||
},
|
borderRadius: BorderRadius.only(
|
||||||
child: Container(
|
topRight: Radius.circular(5.0),
|
||||||
alignment: Alignment.center,
|
bottomRight: Radius.circular(5.0)),
|
||||||
height: 50.0,
|
onTap: () {
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
audio.episodeLoad(widget.episodeItem);
|
||||||
child: Row(
|
},
|
||||||
children: <Widget>[
|
child: Container(
|
||||||
Text('Play Now',
|
alignment: Alignment.center,
|
||||||
style: TextStyle(
|
height: 50.0,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Play Now',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).accentColor,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
)),
|
||||||
|
Icon(
|
||||||
|
Icons.play_arrow,
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
fontSize: 15,
|
),
|
||||||
fontWeight: FontWeight.bold,
|
],
|
||||||
)),
|
|
||||||
Icon(
|
|
||||||
Icons.play_arrow,
|
|
||||||
color: Theme.of(context).accentColor,
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
),
|
: (widget.episodeItem.title == data.item1?.title &&
|
||||||
)
|
data.item2 == true)
|
||||||
: (widget.episodeItem.title == audioplay.episode?.title &&
|
? Container(
|
||||||
audioplay.audioState == AudioState.play)
|
padding: EdgeInsets.only(right: 30),
|
||||||
? Container(
|
child: SizedBox(
|
||||||
padding: EdgeInsets.only(right: 30),
|
width: 20, height: 15, child: WaveLoader()))
|
||||||
child:
|
: Container(
|
||||||
SizedBox(width: 20, height: 15, child: WaveLoader()))
|
padding: EdgeInsets.only(right: 30),
|
||||||
: Container(
|
child: SizedBox(
|
||||||
padding: EdgeInsets.only(right: 30),
|
width: 20,
|
||||||
child: SizedBox(
|
height: 15,
|
||||||
width: 20,
|
child: LineLoader(),
|
||||||
height: 15,
|
),
|
||||||
child: LineLoader(),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -256,7 +256,7 @@ class _SearchResultState extends State<SearchResult> {
|
|||||||
String _uuid = Uuid().v4();
|
String _uuid = Uuid().v4();
|
||||||
File("${dir.path}/$_uuid.png")
|
File("${dir.path}/$_uuid.png")
|
||||||
..writeAsBytesSync(img.encodePng(thumbnail));
|
..writeAsBytesSync(img.encodePng(thumbnail));
|
||||||
|
|
||||||
String _imagePath = "${dir.path}/$_uuid.png";
|
String _imagePath = "${dir.path}/$_uuid.png";
|
||||||
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
|
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
|
||||||
String _author = _p.itunes.author ?? _p.author ?? '';
|
String _author = _p.itunes.author ?? _p.author ?? '';
|
||||||
@ -303,6 +303,11 @@ class _SearchResultState extends State<SearchResult> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: Divider.createBorderSide(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -363,18 +368,19 @@ class _SearchResultState extends State<SearchResult> {
|
|||||||
? Container(
|
? Container(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).primaryColorDark,
|
color: Theme.of(context).accentColor,
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topRight: Radius.circular(15.0),
|
topRight: Radius.circular(15.0),
|
||||||
bottomLeft: Radius.circular(15.0),
|
bottomLeft: Radius.circular(15.0),
|
||||||
bottomRight: Radius.circular(15.0),
|
bottomRight: Radius.circular(15.0),
|
||||||
)),
|
)),
|
||||||
margin: EdgeInsets.only(left: 70, right: 50),
|
margin: EdgeInsets.only(left: 70, right: 50, bottom: 10.0),
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(15.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.onlinePodcast.description.trim(),
|
widget.onlinePodcast.description.trim(),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.bodyText1.copyWith(color: Colors.white),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Center(),
|
: Center(),
|
||||||
|
@ -1,718 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'package:audiofileplayer/audiofileplayer.dart';
|
|
||||||
import 'package:audiofileplayer/audio_system.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:marquee/marquee.dart';
|
|
||||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
|
||||||
import 'package:tsacdop/class/audiostate.dart';
|
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
|
||||||
import 'package:tsacdop/episodes/episodedetail.dart';
|
|
||||||
import 'package:tsacdop/home/audiopanel.dart';
|
|
||||||
import 'package:tsacdop/util/pageroute.dart';
|
|
||||||
|
|
||||||
final Logger _logger = Logger('audiofileplayer');
|
|
||||||
|
|
||||||
class PlayerWidget extends StatefulWidget {
|
|
||||||
@override
|
|
||||||
_PlayerWidgetState createState() => _PlayerWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PlayerWidgetState extends State<PlayerWidget> {
|
|
||||||
static const String replay10ButtonId = 'replay10ButtonId';
|
|
||||||
static const String newReleasesButtonId = 'newReleasesButtonId';
|
|
||||||
static const String likeButtonId = 'likeButtonId';
|
|
||||||
static const String pausenowButtonId = 'pausenowButtonId';
|
|
||||||
static const String forwardButtonId = 'forwardButtonId';
|
|
||||||
|
|
||||||
Audio _backgroundAudio;
|
|
||||||
bool _backgroundAudioPlaying;
|
|
||||||
double _backgroundAudioDurationSeconds;
|
|
||||||
double _backgroundAudioPositionSeconds = 0;
|
|
||||||
bool _remoteAudioLoading;
|
|
||||||
String _remoteErrorMessage;
|
|
||||||
double _seekSliderValue = 0.0;
|
|
||||||
String url;
|
|
||||||
bool _isLoading;
|
|
||||||
Color _c;
|
|
||||||
EpisodeBrief episode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
AudioSystem.instance.addMediaEventListener(_mediaEventListener);
|
|
||||||
_isLoading = false;
|
|
||||||
_remoteAudioLoading = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//open episoddetail page
|
|
||||||
_gotoEpisode() async {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
SlideUptRoute(
|
|
||||||
page: EpisodeDetail(episodeItem: episode, heroTag: 'playpanel')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//init audio player from url
|
|
||||||
void _initbackgroundAudioPlayer(String url) {
|
|
||||||
_remoteErrorMessage = null;
|
|
||||||
_remoteAudioLoading = true;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState = AudioState.load;
|
|
||||||
|
|
||||||
if (_backgroundAudioPlaying == true) {
|
|
||||||
_backgroundAudio?.pause();
|
|
||||||
AudioSystem.instance.stopBackgroundDisplay();
|
|
||||||
}
|
|
||||||
_backgroundAudio?.dispose();
|
|
||||||
_backgroundAudio = Audio.loadFromRemoteUrl(url,
|
|
||||||
onDuration: (double durationSeconds) {
|
|
||||||
setState(() {
|
|
||||||
_backgroundAudioDurationSeconds = durationSeconds;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.play;
|
|
||||||
});
|
|
||||||
_setNotification();
|
|
||||||
},
|
|
||||||
onPosition: (double positionSeconds) {
|
|
||||||
setState(() {
|
|
||||||
if (_backgroundAudioPositionSeconds <
|
|
||||||
_backgroundAudioDurationSeconds) {
|
|
||||||
_seekSliderValue = _backgroundAudioPositionSeconds /
|
|
||||||
_backgroundAudioDurationSeconds;
|
|
||||||
_backgroundAudioPositionSeconds = positionSeconds;
|
|
||||||
} else {
|
|
||||||
_seekSliderValue = 1;
|
|
||||||
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (String message) => setState(() {
|
|
||||||
_remoteErrorMessage = message;
|
|
||||||
_backgroundAudio.dispose();
|
|
||||||
_backgroundAudio = null;
|
|
||||||
_backgroundAudioPlaying = false;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.error;
|
|
||||||
}),
|
|
||||||
onComplete: () => setState(() {
|
|
||||||
_backgroundAudioPlaying = false;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.complete;
|
|
||||||
}),
|
|
||||||
looping: false,
|
|
||||||
playInBackground: true)
|
|
||||||
..play();
|
|
||||||
}
|
|
||||||
|
|
||||||
//init audio player form local file
|
|
||||||
void _initbackgroundAudioPlayerLocal(String path) {
|
|
||||||
_remoteErrorMessage = null;
|
|
||||||
_remoteAudioLoading = true;
|
|
||||||
ByteData audio = getAudio(path);
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState = AudioState.load;
|
|
||||||
if (_backgroundAudioPlaying == true) {
|
|
||||||
_backgroundAudio?.pause();
|
|
||||||
AudioSystem.instance.stopBackgroundDisplay();
|
|
||||||
}
|
|
||||||
_backgroundAudio?.dispose();
|
|
||||||
_backgroundAudio = Audio.loadFromByteData(audio,
|
|
||||||
onDuration: (double durationSeconds) {
|
|
||||||
setState(() {
|
|
||||||
_backgroundAudioDurationSeconds = durationSeconds;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
});
|
|
||||||
_setNotification();
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.play;
|
|
||||||
},
|
|
||||||
onPosition: (double positionSeconds) {
|
|
||||||
setState(() {
|
|
||||||
if (_backgroundAudioPositionSeconds <
|
|
||||||
_backgroundAudioDurationSeconds) {
|
|
||||||
_seekSliderValue = _backgroundAudioPositionSeconds /
|
|
||||||
_backgroundAudioDurationSeconds;
|
|
||||||
_backgroundAudioPositionSeconds = positionSeconds;
|
|
||||||
} else {
|
|
||||||
_seekSliderValue = 1;
|
|
||||||
_backgroundAudioPositionSeconds = _backgroundAudioDurationSeconds;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError: (String message) => setState(() {
|
|
||||||
_remoteErrorMessage = message;
|
|
||||||
_backgroundAudio.dispose();
|
|
||||||
_backgroundAudio = null;
|
|
||||||
_backgroundAudioPlaying = false;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.error;
|
|
||||||
}),
|
|
||||||
onComplete: () => setState(() {
|
|
||||||
_backgroundAudioPlaying = false;
|
|
||||||
_remoteAudioLoading = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.complete;
|
|
||||||
}),
|
|
||||||
looping: false,
|
|
||||||
playInBackground: true)
|
|
||||||
..play();
|
|
||||||
}
|
|
||||||
|
|
||||||
//if downloaded
|
|
||||||
Future<String> _getFile(String url) async {
|
|
||||||
final task = await FlutterDownloader.loadTasksWithRawQuery(
|
|
||||||
query: "SELECT * FROM task WHERE url = '$url' AND status = 3");
|
|
||||||
if (task.length != 0) {
|
|
||||||
String _filePath = task.first.savedDir + '/' + task.first.filename;
|
|
||||||
return _filePath;
|
|
||||||
}
|
|
||||||
return 'NotDownload';
|
|
||||||
}
|
|
||||||
|
|
||||||
//get local audio file
|
|
||||||
ByteData getAudio(String path) {
|
|
||||||
File audioFile = File(path);
|
|
||||||
Uint8List audio = audioFile.readAsBytesSync();
|
|
||||||
return ByteData.view(audio.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
|
|
||||||
final url = Provider.of<AudioPlay>(context).episode?.enclosureUrl;
|
|
||||||
if (url != this.url) {
|
|
||||||
setState(() {
|
|
||||||
this.url = url;
|
|
||||||
episode = Provider.of<AudioPlay>(context).episode;
|
|
||||||
_backgroundAudioPlaying = true;
|
|
||||||
_isLoading = true;
|
|
||||||
_getFile(url).then((result) {
|
|
||||||
result == 'NotDownload'
|
|
||||||
? _initbackgroundAudioPlayer(url)
|
|
||||||
: _initbackgroundAudioPlayerLocal(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
AudioSystem.instance.removeMediaEventListener(_mediaEventListener);
|
|
||||||
_backgroundAudio?.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _stringForSeconds(double seconds) {
|
|
||||||
if (seconds == null) return null;
|
|
||||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
|
||||||
}
|
|
||||||
|
|
||||||
void _mediaEventListener(MediaEvent mediaEvent) {
|
|
||||||
_logger.info('App received media event of type: ${mediaEvent.type}');
|
|
||||||
final MediaActionType type = mediaEvent.type;
|
|
||||||
if (type == MediaActionType.play) {
|
|
||||||
_resumeBackgroundAudio();
|
|
||||||
} else if (type == MediaActionType.pause) {
|
|
||||||
_pauseBackgroundAudio();
|
|
||||||
} else if (type == MediaActionType.playPause) {
|
|
||||||
_backgroundAudioPlaying
|
|
||||||
? _pauseBackgroundAudio()
|
|
||||||
: _resumeBackgroundAudio();
|
|
||||||
} else if (type == MediaActionType.stop) {
|
|
||||||
_stopBackgroundAudio();
|
|
||||||
} else if (type == MediaActionType.seekTo) {
|
|
||||||
_backgroundAudio.seek(mediaEvent.seekToPositionSeconds);
|
|
||||||
AudioSystem.instance
|
|
||||||
.setPlaybackState(true, mediaEvent.seekToPositionSeconds);
|
|
||||||
} else if (type == MediaActionType.skipForward) {
|
|
||||||
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
|
|
||||||
_forwardBackgroundAudio(skipIntervalSeconds);
|
|
||||||
_logger.info(
|
|
||||||
'Skip-forward event had skipIntervalSeconds $skipIntervalSeconds.');
|
|
||||||
} else if (type == MediaActionType.skipBackward) {
|
|
||||||
final double skipIntervalSeconds = mediaEvent.skipIntervalSeconds;
|
|
||||||
_forwardBackgroundAudio(skipIntervalSeconds);
|
|
||||||
_logger.info(
|
|
||||||
'Skip-backward event had skipIntervalSeconds $skipIntervalSeconds.');
|
|
||||||
} else if (type == MediaActionType.custom) {
|
|
||||||
if (mediaEvent.customEventId == replay10ButtonId) {
|
|
||||||
_forwardBackgroundAudio(-10.0);
|
|
||||||
} else if (mediaEvent.customEventId == likeButtonId) {
|
|
||||||
_resumeBackgroundAudio();
|
|
||||||
} else if (mediaEvent.customEventId == forwardButtonId) {
|
|
||||||
_forwardBackgroundAudio(30.0);
|
|
||||||
} else if (mediaEvent.customEventId == pausenowButtonId) {
|
|
||||||
_pauseBackgroundAudio();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final _pauseButton = AndroidCustomMediaButton(
|
|
||||||
'pausenow', pausenowButtonId, 'ic_stat_pause_circle_filled');
|
|
||||||
final _replay10Button = AndroidCustomMediaButton(
|
|
||||||
'replay10', replay10ButtonId, 'ic_stat_replay_10');
|
|
||||||
final _forwardButton = AndroidCustomMediaButton(
|
|
||||||
'forward', forwardButtonId, 'ic_stat_forward_30');
|
|
||||||
final _playnowButton = AndroidCustomMediaButton(
|
|
||||||
'playnow', likeButtonId, 'ic_stat_play_circle_filled');
|
|
||||||
|
|
||||||
Future<void> _setNotification() async {
|
|
||||||
final Uint8List imageBytes = File('${episode.imagePath}').readAsBytesSync();
|
|
||||||
AudioSystem.instance.setMetadata(AudioMetadata(
|
|
||||||
title: episode.title,
|
|
||||||
artist: episode.feedTitle,
|
|
||||||
album: episode.feedTitle,
|
|
||||||
genre: "Podcast",
|
|
||||||
durationSeconds: _backgroundAudioDurationSeconds,
|
|
||||||
artBytes: imageBytes));
|
|
||||||
AudioSystem.instance
|
|
||||||
.setPlaybackState(true, _backgroundAudioPositionSeconds);
|
|
||||||
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
|
||||||
AndroidMediaButtonType.pause,
|
|
||||||
_forwardButton,
|
|
||||||
AndroidMediaButtonType.stop,
|
|
||||||
], androidCompactIndices: <int>[
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
]);
|
|
||||||
|
|
||||||
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
|
||||||
MediaActionType.playPause,
|
|
||||||
MediaActionType.pause,
|
|
||||||
MediaActionType.next,
|
|
||||||
MediaActionType.previous,
|
|
||||||
MediaActionType.skipForward,
|
|
||||||
MediaActionType.skipBackward,
|
|
||||||
MediaActionType.seekTo,
|
|
||||||
MediaActionType.custom,
|
|
||||||
}, skipIntervalSeconds: 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _resumeBackgroundAudio() async {
|
|
||||||
_backgroundAudio.resume();
|
|
||||||
setState(() {
|
|
||||||
_backgroundAudioPlaying = true;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.play;
|
|
||||||
});
|
|
||||||
final Uint8List imageBytes = File('${episode.imagePath}').readAsBytesSync();
|
|
||||||
AudioSystem.instance.setMetadata(AudioMetadata(
|
|
||||||
title: episode.title,
|
|
||||||
artist: episode.feedTitle,
|
|
||||||
album: episode.feedTitle,
|
|
||||||
genre: "Podcast",
|
|
||||||
durationSeconds: _backgroundAudioDurationSeconds,
|
|
||||||
artBytes: imageBytes));
|
|
||||||
|
|
||||||
AudioSystem.instance
|
|
||||||
.setPlaybackState(true, _backgroundAudioPositionSeconds);
|
|
||||||
|
|
||||||
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
|
||||||
AndroidMediaButtonType.pause,
|
|
||||||
_forwardButton,
|
|
||||||
AndroidMediaButtonType.stop,
|
|
||||||
], androidCompactIndices: <int>[
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
]);
|
|
||||||
|
|
||||||
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
|
||||||
MediaActionType.playPause,
|
|
||||||
MediaActionType.pause,
|
|
||||||
MediaActionType.next,
|
|
||||||
MediaActionType.previous,
|
|
||||||
MediaActionType.skipForward,
|
|
||||||
MediaActionType.skipBackward,
|
|
||||||
MediaActionType.seekTo,
|
|
||||||
MediaActionType.custom,
|
|
||||||
}, skipIntervalSeconds: 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _pauseBackgroundAudio() {
|
|
||||||
_backgroundAudio.pause();
|
|
||||||
setState(() {
|
|
||||||
_backgroundAudioPlaying = false;
|
|
||||||
Provider.of<AudioPlay>(context, listen: false).audioState =
|
|
||||||
AudioState.pause;
|
|
||||||
});
|
|
||||||
|
|
||||||
AudioSystem.instance
|
|
||||||
.setPlaybackState(false, _backgroundAudioPositionSeconds);
|
|
||||||
|
|
||||||
AudioSystem.instance.setAndroidNotificationButtons(<dynamic>[
|
|
||||||
AndroidMediaButtonType.play,
|
|
||||||
_forwardButton,
|
|
||||||
AndroidMediaButtonType.stop,
|
|
||||||
], androidCompactIndices: <int>[
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
AudioSystem.instance.setSupportedMediaActions(<MediaActionType>{
|
|
||||||
MediaActionType.playPause,
|
|
||||||
MediaActionType.play,
|
|
||||||
MediaActionType.next,
|
|
||||||
MediaActionType.previous,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _stopBackgroundAudio() {
|
|
||||||
_backgroundAudio.pause();
|
|
||||||
setState(() => _backgroundAudioPlaying = false);
|
|
||||||
AudioSystem.instance.stopBackgroundDisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _forwardBackgroundAudio(double seconds) {
|
|
||||||
final double forwardposition = _backgroundAudioPositionSeconds + seconds;
|
|
||||||
_backgroundAudio.seek(forwardposition);
|
|
||||||
AudioSystem.instance.setPlaybackState(true, forwardposition);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _expandedPanel(BuildContext context) => Container(
|
|
||||||
height: 300,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
height: 80.0,
|
|
||||||
padding: EdgeInsets.all(20),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: (episode.title.length > 10)
|
|
||||||
? Marquee(
|
|
||||||
text: episode.title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold, fontSize: 18),
|
|
||||||
scrollAxis: Axis.horizontal,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
blankSpace: 30.0,
|
|
||||||
velocity: 50.0,
|
|
||||||
pauseAfterRound: Duration(seconds: 1),
|
|
||||||
startPadding: 30.0,
|
|
||||||
accelerationDuration: Duration(seconds: 1),
|
|
||||||
accelerationCurve: Curves.linear,
|
|
||||||
decelerationDuration: Duration(milliseconds: 500),
|
|
||||||
decelerationCurve: Curves.easeOut,
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
episode.title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold, fontSize: 20),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.only(left: 30, right: 30),
|
|
||||||
child: SliderTheme(
|
|
||||||
data: SliderTheme.of(context).copyWith(
|
|
||||||
activeTrackColor: Colors.blue[100],
|
|
||||||
inactiveTrackColor: Colors.grey[300],
|
|
||||||
trackHeight: 3.0,
|
|
||||||
thumbColor: Colors.blue[400],
|
|
||||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0),
|
|
||||||
overlayColor: Colors.blue.withAlpha(32),
|
|
||||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 14.0),
|
|
||||||
),
|
|
||||||
child: Slider(
|
|
||||||
value: _seekSliderValue,
|
|
||||||
onChanged: (double val) {
|
|
||||||
setState(() => _seekSliderValue = val);
|
|
||||||
final double positionSeconds =
|
|
||||||
val * _backgroundAudioDurationSeconds;
|
|
||||||
_backgroundAudio.seek(positionSeconds);
|
|
||||||
AudioSystem.instance
|
|
||||||
.setPlaybackState(true, positionSeconds);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 20.0,
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 50.0),
|
|
||||||
child: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
_stringForSeconds(_backgroundAudioPositionSeconds) ?? '',
|
|
||||||
style: TextStyle(fontSize: 10),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: _remoteErrorMessage != null
|
|
||||||
? Text(_remoteErrorMessage,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: const Color(0xFFFF0000)))
|
|
||||||
: Text(
|
|
||||||
_remoteAudioLoading ? 'Buffring...' : '',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).accentColor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
_stringForSeconds(_backgroundAudioDurationSeconds) ?? '',
|
|
||||||
style: TextStyle(fontSize: 10),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 100,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? () => _forwardBackgroundAudio(-10)
|
|
||||||
: null,
|
|
||||||
iconSize: 32.0,
|
|
||||||
icon: Icon(Icons.replay_10),
|
|
||||||
color: Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
),
|
|
||||||
_backgroundAudioPlaying
|
|
||||||
? Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? () {
|
|
||||||
_pauseBackgroundAudio();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
iconSize: 40.0,
|
|
||||||
icon: Icon(Icons.pause_circle_filled),
|
|
||||||
color:
|
|
||||||
Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
)
|
|
||||||
: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
_resumeBackgroundAudio();
|
|
||||||
},
|
|
||||||
iconSize: 40.0,
|
|
||||||
icon: Icon(Icons.play_circle_filled),
|
|
||||||
color:
|
|
||||||
Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 30.0),
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? () => _forwardBackgroundAudio(30)
|
|
||||||
: null,
|
|
||||||
iconSize: 32.0,
|
|
||||||
icon: Icon(Icons.forward_30),
|
|
||||||
color: Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Container(
|
|
||||||
height: 50.0,
|
|
||||||
margin: EdgeInsets.symmetric(vertical: 10.0),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(10.0)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
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("${episode.imagePath}"))),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => _gotoEpisode(),
|
|
||||||
child: Icon(Icons.info),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
))
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
Widget _miniPanel(double width, BuildContext context) {
|
|
||||||
var color = json.decode(episode?.primaryColor);
|
|
||||||
if (color) {
|
|
||||||
if (Theme.of(context).brightness == Brightness.light) {
|
|
||||||
_c = (color[0] > 200 && color[1] > 200 && color[2] > 200)
|
|
||||||
? Color.fromRGBO(
|
|
||||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
|
||||||
: Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
|
||||||
} else {
|
|
||||||
_c = (color[0] < 50 && color[1] < 50 && color[2] < 50)
|
|
||||||
? Color.fromRGBO(
|
|
||||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
|
||||||
: Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Container(
|
|
||||||
height: 60,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
child:
|
|
||||||
Column(mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[
|
|
||||||
SizedBox(
|
|
||||||
height: 2,
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: _seekSliderValue,
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(_c),
|
|
||||||
)),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.only(left: 15, right: 10),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
flex: 4,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(vertical: 5),
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: (episode.title.length > 55)
|
|
||||||
? Marquee(
|
|
||||||
text: episode.title,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
scrollAxis: Axis.vertical,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
blankSpace: 30.0,
|
|
||||||
velocity: 50.0,
|
|
||||||
pauseAfterRound: Duration(seconds: 1),
|
|
||||||
startPadding: 30.0,
|
|
||||||
accelerationDuration: Duration(seconds: 1),
|
|
||||||
accelerationCurve: Curves.linear,
|
|
||||||
decelerationDuration: Duration(milliseconds: 500),
|
|
||||||
decelerationCurve: Curves.easeOut,
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
episode.title,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: _remoteAudioLoading
|
|
||||||
? Text(
|
|
||||||
'Buffring...',
|
|
||||||
style:
|
|
||||||
TextStyle(color: Theme.of(context).accentColor),
|
|
||||||
)
|
|
||||||
: Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
_stringForSeconds(
|
|
||||||
_backgroundAudioDurationSeconds -
|
|
||||||
_backgroundAudioPositionSeconds) ??
|
|
||||||
'',
|
|
||||||
style: TextStyle(color: _c),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' Left',
|
|
||||||
style: TextStyle(color: _c),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
_backgroundAudioPlaying
|
|
||||||
? Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? () {
|
|
||||||
_pauseBackgroundAudio();
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
iconSize: 25.0,
|
|
||||||
icon: Icon(Icons.pause_circle_filled),
|
|
||||||
color:
|
|
||||||
Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
)
|
|
||||||
: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
_resumeBackgroundAudio();
|
|
||||||
},
|
|
||||||
iconSize: 25.0,
|
|
||||||
icon: Icon(Icons.play_circle_filled),
|
|
||||||
color:
|
|
||||||
Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
),
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: _backgroundAudioPlaying
|
|
||||||
? () => _forwardBackgroundAudio(30)
|
|
||||||
: null,
|
|
||||||
iconSize: 25.0,
|
|
||||||
icon: Icon(Icons.forward_30),
|
|
||||||
color: Theme.of(context).tabBarTheme.labelColor),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
double _width = MediaQuery.of(context).size.width;
|
|
||||||
|
|
||||||
return !_isLoading
|
|
||||||
? Center()
|
|
||||||
: AudioPanel(
|
|
||||||
miniPanel: _miniPanel(_width, context),
|
|
||||||
expandedPanel: _expandedPanel(context));
|
|
||||||
}
|
|
||||||
}
|
|
@ -46,7 +46,9 @@ class _AudioPanelState extends State<AudioPanel>
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _backToMini(),
|
onTap: () => _backToMini(),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.5),
|
color: Theme.of(context)
|
||||||
|
.scaffoldBackgroundColor
|
||||||
|
.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -75,7 +77,16 @@ class _AudioPanelState extends State<AudioPanel>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(
|
: Container(
|
||||||
color: Theme.of(context).primaryColor,
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
offset: Offset(0, -1),
|
||||||
|
blurRadius: 4,
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: _animation.value < (maxSize - 50)
|
opacity: _animation.value < (maxSize - 50)
|
||||||
@ -99,7 +110,7 @@ class _AudioPanelState extends State<AudioPanel>
|
|||||||
setState(() {
|
setState(() {
|
||||||
_animation =
|
_animation =
|
||||||
Tween<double>(begin: initSize, end: minSize).animate(_controller);
|
Tween<double>(begin: initSize, end: minSize).animate(_controller);
|
||||||
initSize = minSize;
|
initSize = minSize;
|
||||||
});
|
});
|
||||||
_controller.forward();
|
_controller.forward();
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'hometab.dart';
|
import 'hometab.dart';
|
||||||
import 'package:tsacdop/home/appbar/importompl.dart';
|
import 'package:tsacdop/home/appbar/importompl.dart';
|
||||||
import 'package:tsacdop/home/audio_player.dart';
|
import 'package:tsacdop/home/audioplayer.dart';
|
||||||
import 'homescroll.dart';
|
import 'homescroll.dart';
|
||||||
|
|
||||||
class Home extends StatelessWidget {
|
class Home extends StatelessWidget {
|
||||||
|
@ -263,7 +263,10 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||||||
.map<Widget>((PodcastLocal podcastLocal) {
|
.map<Widget>((PodcastLocal podcastLocal) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).primaryColor),
|
color: Theme.of(context).brightness ==
|
||||||
|
Brightness.light
|
||||||
|
? Theme.of(context).primaryColor
|
||||||
|
: Colors.black12),
|
||||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||||
key: ObjectKey(podcastLocal.title),
|
key: ObjectKey(podcastLocal.title),
|
||||||
child: PodcastPreview(
|
child: PodcastPreview(
|
||||||
@ -426,19 +429,23 @@ class ShowEpisode extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
width: 3.0,
|
? Theme.of(context).primaryColor
|
||||||
),
|
: Theme.of(context).scaffoldBackgroundColor,
|
||||||
boxShadow: [
|
// color: Theme.of(context).primaryColor,
|
||||||
BoxShadow(
|
width: 3.0,
|
||||||
color: Theme.of(context).primaryColor,
|
),
|
||||||
blurRadius: 1.0,
|
// boxShadow: [
|
||||||
spreadRadius: 0.5,
|
// BoxShadow(
|
||||||
),
|
// color: Theme.of(context).primaryColor,
|
||||||
]),
|
// blurRadius: 1.0,
|
||||||
|
// spreadRadius: 0.5,
|
||||||
|
// ),
|
||||||
|
// ]
|
||||||
|
),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(10.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -42,4 +42,16 @@ class KeyValueStorage {
|
|||||||
if(prefs.getInt(key) == null) await prefs.setInt(key, 0);
|
if(prefs.getInt(key) == null) await prefs.setInt(key, 0);
|
||||||
return prefs.getInt(key);
|
return prefs.getInt(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> savePlaylist(List<String> playList) async{
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.setStringList(key, playList);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> getPlayList() async{
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
if(prefs.getStringList(key) == null) {await prefs.setStringList(key, []);}
|
||||||
|
print(prefs.getStringList(key).toString());
|
||||||
|
return prefs.getStringList(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:flutter_downloader/flutter_downloader.dart';
|
|||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:tsacdop/class/podcastlocal.dart';
|
import 'package:tsacdop/class/podcastlocal.dart';
|
||||||
|
import 'package:tsacdop/class/audiostate.dart';
|
||||||
import 'package:tsacdop/class/episodebrief.dart';
|
import 'package:tsacdop/class/episodebrief.dart';
|
||||||
import 'package:tsacdop/webfeed/webfeed.dart';
|
import 'package:tsacdop/webfeed/webfeed.dart';
|
||||||
|
|
||||||
@ -36,12 +37,14 @@ class DBHelper {
|
|||||||
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
||||||
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
|
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
|
||||||
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
|
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
|
||||||
|
await db.execute(
|
||||||
|
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT UNIQUE,
|
||||||
|
seconds INTEGER, seek_value INTEGER, add_date INTEGER)""");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
|
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
List<PodcastLocal> podcastLocal = List();
|
List<PodcastLocal> podcastLocal = List();
|
||||||
|
|
||||||
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(
|
||||||
@ -66,7 +69,7 @@ class DBHelper {
|
|||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, email, provider, link FROM PodcastLocal ORDER BY add_date DESC');
|
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, email, provider, link FROM PodcastLocal ORDER BY add_date DESC');
|
||||||
|
|
||||||
List<PodcastLocal> podcastLocal = List();
|
List<PodcastLocal> podcastLocal = List();
|
||||||
|
|
||||||
for (int i = 0; i < list.length; i++) {
|
for (int i = 0; i < list.length; i++) {
|
||||||
@ -78,7 +81,7 @@ class DBHelper {
|
|||||||
list[i]['author'],
|
list[i]['author'],
|
||||||
list[i]['id'],
|
list[i]['id'],
|
||||||
list[i]['imagePath'],
|
list[i]['imagePath'],
|
||||||
list.first['email'],
|
list.first['email'],
|
||||||
list.first['provider'],
|
list.first['provider'],
|
||||||
list.first['link']));
|
list.first['link']));
|
||||||
}
|
}
|
||||||
@ -132,6 +135,24 @@ class DBHelper {
|
|||||||
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
|
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> saveHistory(PlayHistory history) async {
|
||||||
|
var dbClient = await database;
|
||||||
|
int _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
int result = await dbClient.transaction((txn) async {
|
||||||
|
return await txn.rawInsert(
|
||||||
|
"""REPLACE INTO PlayHistory (title, enclosure_url, seconds, seek_value, add_date)
|
||||||
|
VALUES (?, ?, ?, ?, ?) """,
|
||||||
|
[
|
||||||
|
history.title,
|
||||||
|
history.url,
|
||||||
|
history.seconds,
|
||||||
|
history.seekValue,
|
||||||
|
_milliseconds
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
DateTime _parsePubDate(String pubDate) {
|
DateTime _parsePubDate(String pubDate) {
|
||||||
if (pubDate == null) return DateTime.now();
|
if (pubDate == null) return DateTime.now();
|
||||||
print(pubDate);
|
print(pubDate);
|
||||||
@ -156,11 +177,13 @@ class DBHelper {
|
|||||||
if (year != null && time != null && month != null) {
|
if (year != null && time != null && month != null) {
|
||||||
date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
|
date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
|
||||||
.parse(month + year + time);
|
.parse(month + year + time);
|
||||||
} else if(year!=null && time!=null && month == null){
|
} else if (year != null && time != null && month == null) {
|
||||||
String month = mmDd.stringMatch(pubDate);
|
String month = mmDd.stringMatch(pubDate);
|
||||||
date = DateFormat('mm-dd yyyy HH:mm', 'en_US')
|
date = DateFormat('mm-dd yyyy HH:mm', 'en_US')
|
||||||
.parse(month +' ' + year +' '+ time);
|
.parse(month + ' ' + year + ' ' + time);
|
||||||
} else {date = DateTime.now();}
|
} else {
|
||||||
|
date = DateTime.now();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -488,8 +511,8 @@ class DBHelper {
|
|||||||
|
|
||||||
Future<String> getFeedDescription(String id) async {
|
Future<String> getFeedDescription(String id) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient
|
||||||
'SELECT description FROM PodcastLocal WHERE id = ?', [id]);
|
.rawQuery('SELECT description FROM PodcastLocal WHERE id = ?', [id]);
|
||||||
String description = list[0]['description'];
|
String description = list[0]['description'];
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ void main() async {
|
|||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => AudioPlay()),
|
ChangeNotifierProvider(create: (context) => AudioPlayer()),
|
||||||
ChangeNotifierProvider(create: (context) => ImportOmpl()),
|
ChangeNotifierProvider(create: (context) => ImportOmpl()),
|
||||||
ChangeNotifierProvider(create: (context) => SettingState()),
|
ChangeNotifierProvider(create: (context) => SettingState()),
|
||||||
ChangeNotifierProvider(create: (context) => GroupList()),
|
ChangeNotifierProvider(create: (context) => GroupList()),
|
||||||
@ -29,10 +29,11 @@ class MyApp extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Provider.of<SettingState>(context).theme;
|
var theme = Provider.of<SettingState>(context).theme;
|
||||||
|
print(theme);
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
themeMode: theme == 0
|
themeMode: theme == 0
|
||||||
? ThemeMode.system
|
? ThemeMode.system
|
||||||
: theme == 1 ? ThemeMode.dark : ThemeMode.light,
|
: theme == 1 ? ThemeMode.light: ThemeMode.dark,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
title: 'TsacDop',
|
title: 'TsacDop',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
@ -15,6 +15,7 @@ import 'package:tsacdop/episodes/episodedetail.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/pageroute.dart';
|
import 'package:tsacdop/util/pageroute.dart';
|
||||||
|
import 'package:tsacdop/home/audioplayer.dart';
|
||||||
|
|
||||||
class PodcastDetail extends StatefulWidget {
|
class PodcastDetail extends StatefulWidget {
|
||||||
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
|
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
|
||||||
@ -73,11 +74,11 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||||||
double _width = MediaQuery.of(context).size.width;
|
double _width = MediaQuery.of(context).size.width;
|
||||||
Color _color;
|
Color _color;
|
||||||
var color = json.decode(widget.podcastLocal.primaryColor);
|
var color = json.decode(widget.podcastLocal.primaryColor);
|
||||||
(color[0] > 200 && color[1] > 200 && color[2] > 200)
|
(color[0] > 200 && color[1] > 200 && color[2] > 200)
|
||||||
? _color = Color.fromRGBO(
|
? _color = Color.fromRGBO(
|
||||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
||||||
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
||||||
|
|
||||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarIconBrightness: Brightness.light,
|
statusBarIconBrightness: Brightness.light,
|
||||||
@ -91,286 +92,317 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||||||
key: _refreshIndicatorKey,
|
key: _refreshIndicatorKey,
|
||||||
color: Theme.of(context).accentColor,
|
color: Theme.of(context).accentColor,
|
||||||
onRefresh: () => _updateRssItem(widget.podcastLocal),
|
onRefresh: () => _updateRssItem(widget.podcastLocal),
|
||||||
child: FutureBuilder<List<EpisodeBrief>>(
|
child: Stack(
|
||||||
future: _getRssItem(widget.podcastLocal),
|
children: <Widget>[
|
||||||
builder: (context, snapshot) {
|
FutureBuilder<List<EpisodeBrief>>(
|
||||||
if (snapshot.hasError) print(snapshot.error);
|
future: _getRssItem(widget.podcastLocal),
|
||||||
return (snapshot.hasData)
|
builder: (context, snapshot) {
|
||||||
? CustomScrollView(
|
if (snapshot.hasError) print(snapshot.error);
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
return (snapshot.hasData)
|
||||||
primary: true,
|
? CustomScrollView(
|
||||||
slivers: <Widget>[
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
SliverAppBar(
|
primary: true,
|
||||||
elevation: 0,
|
slivers: <Widget>[
|
||||||
iconTheme: IconThemeData(color: Colors.white),
|
SliverAppBar(
|
||||||
expandedHeight: 170,
|
elevation: 0,
|
||||||
backgroundColor: _color,
|
iconTheme: IconThemeData(color: Colors.white),
|
||||||
floating: true,
|
expandedHeight: 170,
|
||||||
pinned: true,
|
backgroundColor: _color,
|
||||||
flexibleSpace: LayoutBuilder(builder:
|
floating: true,
|
||||||
(BuildContext context,
|
pinned: true,
|
||||||
BoxConstraints constraints) {
|
flexibleSpace: LayoutBuilder(builder:
|
||||||
top = constraints.biggest.height;
|
(BuildContext context,
|
||||||
return FlexibleSpaceBar(
|
BoxConstraints constraints) {
|
||||||
background: Stack(
|
top = constraints.biggest.height;
|
||||||
children: <Widget>[
|
return FlexibleSpaceBar(
|
||||||
Container(
|
background: Stack(
|
||||||
margin: EdgeInsets.only(top: 120),
|
children: <Widget>[
|
||||||
padding: EdgeInsets.only(left: 80, right: 120),
|
Container(
|
||||||
color: Colors.white10,
|
margin: EdgeInsets.only(top: 120),
|
||||||
alignment: Alignment.centerLeft,
|
padding: EdgeInsets.only(
|
||||||
child: Column(
|
left: 80, right: 120),
|
||||||
mainAxisAlignment:
|
color: Colors.white10,
|
||||||
MainAxisAlignment.start,
|
alignment: Alignment.centerLeft,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
crossAxisAlignment:
|
mainAxisAlignment:
|
||||||
CrossAxisAlignment.start,
|
MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(widget.podcastLocal.author ?? '',
|
crossAxisAlignment:
|
||||||
style: Theme.of(context)
|
CrossAxisAlignment.start,
|
||||||
.textTheme
|
children: <Widget>[
|
||||||
.bodyText1
|
Text(
|
||||||
.copyWith(color: Colors.grey[300])),
|
widget.podcastLocal.author ??
|
||||||
Text(widget.podcastLocal.provider ?? '',
|
'',
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyText1
|
.bodyText1
|
||||||
.copyWith(color: Colors.grey[300]))
|
.copyWith(
|
||||||
],
|
color:
|
||||||
),
|
Colors.grey[300])),
|
||||||
|
Text(
|
||||||
|
widget.podcastLocal.provider ??
|
||||||
|
'',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyText1
|
||||||
|
.copyWith(
|
||||||
|
color:
|
||||||
|
Colors.grey[300]))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
padding: EdgeInsets.only(right: 10),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 120,
|
||||||
|
child: Image.file(File(
|
||||||
|
"${widget.podcastLocal.imagePath}")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: podcastInfo(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Container(
|
title: top < 70
|
||||||
alignment: Alignment.centerRight,
|
? Text(widget.podcastLocal.title,
|
||||||
padding: EdgeInsets.only(right: 10),
|
style: TextStyle(color: Colors.white))
|
||||||
child: SizedBox(
|
: Center(),
|
||||||
height: 120,
|
);
|
||||||
child: Image.file(File(
|
}),
|
||||||
"${widget.podcastLocal.imagePath}")),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: podcastInfo(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
title: top < 70
|
|
||||||
? Text(widget.podcastLocal.title,
|
|
||||||
style: TextStyle(color: Colors.white))
|
|
||||||
: Center(),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
left: 10.0,
|
|
||||||
right: 10.0,
|
|
||||||
top: 20.0,
|
|
||||||
bottom: 10.0),
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
color:
|
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
child: AboutPodcast(
|
|
||||||
podcastLocal: widget.podcastLocal),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverPadding(
|
|
||||||
padding: const EdgeInsets.all(5.0),
|
|
||||||
sliver: SliverGrid(
|
|
||||||
gridDelegate:
|
|
||||||
SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
childAspectRatio: 1.0,
|
|
||||||
crossAxisCount: 3,
|
|
||||||
mainAxisSpacing: 6.0,
|
|
||||||
crossAxisSpacing: 6.0,
|
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
SliverList(
|
||||||
(BuildContext context, int index) {
|
delegate: SliverChildBuilderDelegate(
|
||||||
EpisodeBrief episodeBrief =
|
(BuildContext context, int index) {
|
||||||
snapshot.data[index];
|
return Container(
|
||||||
Color _c;
|
padding: EdgeInsets.only(
|
||||||
var color = json
|
left: 10.0,
|
||||||
.decode(widget.podcastLocal.primaryColor);
|
right: 10.0,
|
||||||
if (Theme.of(context).brightness ==
|
top: 20.0,
|
||||||
Brightness.light) {
|
bottom: 10.0),
|
||||||
(color[0] > 200 &&
|
alignment: Alignment.topLeft,
|
||||||
color[1] > 200 &&
|
color: Theme.of(context)
|
||||||
color[2] > 200)
|
.scaffoldBackgroundColor,
|
||||||
? _c = Color.fromRGBO((255 - color[0]),
|
child: AboutPodcast(
|
||||||
255 - color[1], 255 - color[2], 1.0)
|
podcastLocal: widget.podcastLocal),
|
||||||
: _c = Color.fromRGBO(
|
);
|
||||||
color[0], color[1], color[2], 1.0);
|
},
|
||||||
} else {
|
childCount: 1,
|
||||||
(color[0] < 50 &&
|
),
|
||||||
color[1] < 50 &&
|
),
|
||||||
color[2] < 50)
|
SliverPadding(
|
||||||
? _c = Color.fromRGBO((255 - color[0]),
|
padding: const EdgeInsets.all(5.0),
|
||||||
255 - color[1], 255 - color[2], 1.0)
|
sliver: SliverGrid(
|
||||||
: _c = Color.fromRGBO(
|
gridDelegate:
|
||||||
color[0], color[1], color[2], 1.0);
|
SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
}
|
childAspectRatio: 1.0,
|
||||||
return Material(
|
crossAxisCount: 3,
|
||||||
color: Colors.transparent,
|
mainAxisSpacing: 6.0,
|
||||||
child: InkWell(
|
crossAxisSpacing: 6.0,
|
||||||
onTap: () {
|
),
|
||||||
Navigator.push(
|
delegate: SliverChildBuilderDelegate(
|
||||||
context,
|
(BuildContext context, int index) {
|
||||||
ScaleRoute(
|
EpisodeBrief episodeBrief =
|
||||||
page: EpisodeDetail(
|
snapshot.data[index];
|
||||||
episodeItem: episodeBrief,
|
Color _c;
|
||||||
heroTag: 'podcast',
|
var color = json.decode(
|
||||||
)),
|
widget.podcastLocal.primaryColor);
|
||||||
);
|
if (Theme.of(context).brightness ==
|
||||||
},
|
Brightness.light) {
|
||||||
child: Container(
|
(color[0] > 200 &&
|
||||||
decoration: BoxDecoration(
|
color[1] > 200 &&
|
||||||
borderRadius: BorderRadius.all(
|
color[2] > 200)
|
||||||
Radius.circular(5.0)),
|
? _c = Color.fromRGBO(
|
||||||
color: Theme.of(context)
|
(255 - color[0]),
|
||||||
.scaffoldBackgroundColor,
|
255 - color[1],
|
||||||
border: Border.all(
|
255 - color[2],
|
||||||
color: Theme.of(context)
|
1.0)
|
||||||
.brightness ==
|
: _c = Color.fromRGBO(color[0],
|
||||||
Brightness.light
|
color[1], color[2], 1.0);
|
||||||
? Theme.of(context).primaryColor
|
} else {
|
||||||
: Theme.of(context)
|
(color[0] < 50 &&
|
||||||
.scaffoldBackgroundColor,
|
color[1] < 50 &&
|
||||||
width: 3.0,
|
color[2] < 50)
|
||||||
),
|
? _c = Color.fromRGBO(
|
||||||
boxShadow: [
|
(255 - color[0]),
|
||||||
BoxShadow(
|
255 - color[1],
|
||||||
|
255 - color[2],
|
||||||
|
1.0)
|
||||||
|
: _c = Color.fromRGBO(color[0],
|
||||||
|
color[1], color[2], 1.0);
|
||||||
|
}
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
ScaleRoute(
|
||||||
|
page: EpisodeDetail(
|
||||||
|
episodeItem: episodeBrief,
|
||||||
|
heroTag: 'podcast',
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(5.0)),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.primaryColor,
|
.scaffoldBackgroundColor,
|
||||||
blurRadius: 0.5,
|
border: Border.all(
|
||||||
spreadRadius: 0.5,
|
color: Theme.of(context)
|
||||||
),
|
.brightness ==
|
||||||
]),
|
Brightness.light
|
||||||
alignment: Alignment.center,
|
? Theme.of(context)
|
||||||
padding: EdgeInsets.all(8.0),
|
.primaryColor
|
||||||
child: Column(
|
: Theme.of(context)
|
||||||
mainAxisAlignment:
|
.scaffoldBackgroundColor,
|
||||||
MainAxisAlignment.center,
|
width: 3.0,
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
flex: 2,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Hero(
|
|
||||||
tag: episodeBrief
|
|
||||||
.enclosureUrl +
|
|
||||||
'podcast',
|
|
||||||
child: Container(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.all(
|
|
||||||
Radius.circular(
|
|
||||||
_width / 32)),
|
|
||||||
child: Container(
|
|
||||||
height: _width / 16,
|
|
||||||
width: _width / 16,
|
|
||||||
child: Image.file(File(
|
|
||||||
"${episodeBrief.imagePath}")),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Spacer(),
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.topRight,
|
|
||||||
child: Text(
|
|
||||||
(snapshot.data.length -
|
|
||||||
index)
|
|
||||||
.toString(),
|
|
||||||
style: GoogleFonts.teko(
|
|
||||||
textStyle: TextStyle(
|
|
||||||
fontSize: _width / 24,
|
|
||||||
color: _c,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
flex: 5,
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.topLeft,
|
|
||||||
padding:
|
|
||||||
EdgeInsets.only(top: 2.0),
|
|
||||||
child: Text(
|
|
||||||
episodeBrief.title,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: _width / 32,
|
|
||||||
),
|
|
||||||
maxLines: 4,
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
),
|
),
|
||||||
),
|
boxShadow: [
|
||||||
),
|
BoxShadow(
|
||||||
Expanded(
|
color: Theme.of(context)
|
||||||
flex: 1,
|
.primaryColor,
|
||||||
child: Row(
|
blurRadius: 0.5,
|
||||||
children: <Widget>[
|
spreadRadius: 0.5,
|
||||||
Align(
|
|
||||||
alignment:
|
|
||||||
Alignment.bottomLeft,
|
|
||||||
child: Text(
|
|
||||||
episodeBrief.dateToString(),
|
|
||||||
//podcast[index].pubDate.substring(4, 16),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: _width / 35,
|
|
||||||
color: _c,
|
|
||||||
fontStyle:
|
|
||||||
FontStyle.italic),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Spacer(),
|
]),
|
||||||
DownloadIcon(
|
alignment: Alignment.center,
|
||||||
episodeBrief: episodeBrief),
|
padding: EdgeInsets.all(8.0),
|
||||||
Padding(
|
child: Column(
|
||||||
padding: EdgeInsets.all(1),
|
mainAxisAlignment:
|
||||||
),
|
MainAxisAlignment.center,
|
||||||
Container(
|
children: <Widget>[
|
||||||
alignment:
|
Expanded(
|
||||||
Alignment.bottomRight,
|
flex: 2,
|
||||||
child: (episodeBrief.liked ==
|
child: Row(
|
||||||
0)
|
mainAxisAlignment:
|
||||||
? Center()
|
MainAxisAlignment.start,
|
||||||
: IconTheme(
|
children: <Widget>[
|
||||||
data: IconThemeData(
|
Hero(
|
||||||
size: 15),
|
tag: episodeBrief
|
||||||
child: Icon(
|
.enclosureUrl +
|
||||||
Icons.favorite,
|
'podcast',
|
||||||
color: Colors.red,
|
child: Container(
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.all(
|
||||||
|
Radius.circular(
|
||||||
|
_width /
|
||||||
|
32)),
|
||||||
|
child: Container(
|
||||||
|
height: _width / 16,
|
||||||
|
width: _width / 16,
|
||||||
|
child: Image.file(File(
|
||||||
|
"${episodeBrief.imagePath}")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Container(
|
||||||
|
alignment:
|
||||||
|
Alignment.topRight,
|
||||||
|
child: Text(
|
||||||
|
(snapshot.data.length -
|
||||||
|
index)
|
||||||
|
.toString(),
|
||||||
|
style: GoogleFonts.teko(
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
_width / 24,
|
||||||
|
color: _c,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(top: 2.0),
|
||||||
|
child: Text(
|
||||||
|
episodeBrief.title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: _width / 32,
|
||||||
|
),
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Align(
|
||||||
|
alignment:
|
||||||
|
Alignment.bottomLeft,
|
||||||
|
child: Text(
|
||||||
|
episodeBrief
|
||||||
|
.dateToString(),
|
||||||
|
//podcast[index].pubDate.substring(4, 16),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
_width / 35,
|
||||||
|
color: _c,
|
||||||
|
fontStyle: FontStyle
|
||||||
|
.italic),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
DownloadIcon(
|
||||||
|
episodeBrief:
|
||||||
|
episodeBrief),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.all(1),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment:
|
||||||
|
Alignment.bottomRight,
|
||||||
|
child: (episodeBrief
|
||||||
|
.liked ==
|
||||||
|
0)
|
||||||
|
? Center()
|
||||||
|
: IconTheme(
|
||||||
|
data:
|
||||||
|
IconThemeData(
|
||||||
|
size: 15),
|
||||||
|
child: Icon(
|
||||||
|
Icons.favorite,
|
||||||
|
color:
|
||||||
|
Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
);
|
childCount: snapshot.data.length,
|
||||||
},
|
),
|
||||||
childCount: snapshot.data.length,
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
)
|
||||||
],
|
: Center(child: CircularProgressIndicator());
|
||||||
)
|
},
|
||||||
: Center(child: CircularProgressIndicator());
|
),
|
||||||
},
|
Container(child: PlayerWidget()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@ -428,21 +460,23 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
|||||||
},
|
},
|
||||||
child: !_expand
|
child: !_expand
|
||||||
? Column(
|
? Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
_description,
|
_description,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Icon(Icons.keyboard_arrow_down,),
|
child: Icon(
|
||||||
),
|
Icons.keyboard_arrow_down,
|
||||||
],
|
),
|
||||||
)
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
: Text(_description),
|
: Text(_description),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -165,213 +165,223 @@ class _PodcastCardState extends State<PodcastCard> {
|
|||||||
var _groupList = Provider.of<GroupList>(context);
|
var _groupList = Provider.of<GroupList>(context);
|
||||||
_belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
|
_belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
|
||||||
|
|
||||||
return Column(
|
return Container(
|
||||||
mainAxisSize: MainAxisSize.min,
|
decoration: BoxDecoration(
|
||||||
children: <Widget>[
|
border: Border(
|
||||||
InkWell(
|
bottom: Divider.createBorderSide(context),
|
||||||
onTap: () => setState(() => _loadMenu = !_loadMenu),
|
),
|
||||||
child: Container(
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
child: Column(
|
||||||
height: 100,
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Row(
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
InkWell(
|
||||||
mainAxisSize: MainAxisSize.min,
|
onTap: () => setState(() => _loadMenu = !_loadMenu),
|
||||||
children: <Widget>[
|
child: Container(
|
||||||
Container(
|
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||||
child: Icon(
|
height: 100,
|
||||||
Icons.unfold_more,
|
child: Row(
|
||||||
color: _c,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
child: ClipRRect(
|
child: Icon(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
Icons.unfold_more,
|
||||||
child: Container(
|
color: _c,
|
||||||
height: 60,
|
|
||||||
width: 60,
|
|
||||||
child: Image.file(
|
|
||||||
File("${widget.podcastLocal.imagePath}")),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Container(
|
||||||
Container(
|
child: ClipRRect(
|
||||||
width: _width / 2,
|
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
child: Container(
|
||||||
alignment: Alignment.centerLeft,
|
height: 60,
|
||||||
child: Column(
|
width: 60,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
child: Image.file(
|
||||||
mainAxisSize: MainAxisSize.min,
|
File("${widget.podcastLocal.imagePath}")),
|
||||||
children: <Widget>[
|
),
|
||||||
Container(
|
),
|
||||||
alignment: Alignment.centerLeft,
|
),
|
||||||
child: Text(
|
Container(
|
||||||
widget.podcastLocal.title,
|
width: _width / 2,
|
||||||
maxLines: 2,
|
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||||
overflow: TextOverflow.fade,
|
alignment: Alignment.centerLeft,
|
||||||
style: TextStyle(
|
child: Column(
|
||||||
fontWeight: FontWeight.bold, fontSize: 15),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: _belongGroups.map((group) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(right: 5.0),
|
|
||||||
child: Text(group.name));
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
Spacer(),
|
|
||||||
Icon(_loadMenu
|
|
||||||
? Icons.keyboard_arrow_up
|
|
||||||
: Icons.keyboard_arrow_down),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
!_loadMenu
|
|
||||||
? Center()
|
|
||||||
: Container(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Theme.of(context).primaryColorDark),
|
|
||||||
top: BorderSide(
|
|
||||||
color: Theme.of(context).primaryColorDark))),
|
|
||||||
height: 50,
|
|
||||||
child: _addGroup
|
|
||||||
? Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Container(
|
||||||
flex: 4,
|
alignment: Alignment.centerLeft,
|
||||||
child: SingleChildScrollView(
|
child: Text(
|
||||||
scrollDirection: Axis.horizontal,
|
widget.podcastLocal.title,
|
||||||
child: Row(
|
maxLines: 2,
|
||||||
children: _groupList.groups
|
overflow: TextOverflow.fade,
|
||||||
.map<Widget>((PodcastGroup group) {
|
style: TextStyle(
|
||||||
return Container(
|
fontWeight: FontWeight.bold, fontSize: 15),
|
||||||
padding: EdgeInsets.only(left: 5.0),
|
|
||||||
child: FilterChip(
|
|
||||||
key: ValueKey<String>(group.id),
|
|
||||||
label: Text(group.name),
|
|
||||||
selected: _selectedGroups.contains(group),
|
|
||||||
onSelected: (bool value) {
|
|
||||||
setState(() {
|
|
||||||
if (!value) {
|
|
||||||
_selectedGroups.remove(group);
|
|
||||||
print(group.name);
|
|
||||||
} else {
|
|
||||||
_selectedGroups.add(group);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList()),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Row(
|
||||||
flex: 1,
|
children: _belongGroups.map((group) {
|
||||||
child: Row(
|
return Container(
|
||||||
children: <Widget>[
|
padding: EdgeInsets.only(right: 5.0),
|
||||||
IconButton(
|
child: Text(group.name));
|
||||||
icon: Icon(Icons.clear),
|
}).toList(),
|
||||||
onPressed: () => setState(() {
|
),
|
||||||
_addGroup = false;
|
],
|
||||||
}),
|
)),
|
||||||
),
|
Spacer(),
|
||||||
IconButton(
|
Icon(_loadMenu
|
||||||
onPressed: () async {
|
? Icons.keyboard_arrow_up
|
||||||
print(_selectedGroups);
|
: Icons.keyboard_arrow_down),
|
||||||
if (_selectedGroups.length > 0) {
|
Padding(
|
||||||
setState(() {
|
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||||
_addGroup = false;
|
),
|
||||||
});
|
]),
|
||||||
await _groupList.changeGroup(
|
),
|
||||||
widget.podcastLocal.id,
|
),
|
||||||
_selectedGroups,
|
!_loadMenu
|
||||||
);
|
? Center()
|
||||||
Fluttertoast.showToast(
|
: Container(
|
||||||
msg: 'Setting Saved',
|
child: Container(
|
||||||
gravity: ToastGravity.BOTTOM,
|
decoration: BoxDecoration(
|
||||||
);
|
color: Theme.of(context).primaryColor,
|
||||||
} else
|
border: Border(
|
||||||
Fluttertoast.showToast(
|
bottom: BorderSide(
|
||||||
msg: 'At least select one group',
|
color: Theme.of(context).primaryColorDark),
|
||||||
gravity: ToastGravity.BOTTOM,
|
top: BorderSide(
|
||||||
);
|
color: Theme.of(context).primaryColorDark))),
|
||||||
},
|
height: 50,
|
||||||
icon: Icon(Icons.done),
|
child: _addGroup
|
||||||
),
|
? Row(
|
||||||
],
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: _groupList.groups
|
||||||
|
.map<Widget>((PodcastGroup group) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(left: 5.0),
|
||||||
|
child: FilterChip(
|
||||||
|
key: ValueKey<String>(group.id),
|
||||||
|
label: Text(group.name),
|
||||||
|
selected:
|
||||||
|
_selectedGroups.contains(group),
|
||||||
|
onSelected: (bool value) {
|
||||||
|
setState(() {
|
||||||
|
if (!value) {
|
||||||
|
_selectedGroups.remove(group);
|
||||||
|
print(group.name);
|
||||||
|
} else {
|
||||||
|
_selectedGroups.add(group);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList()),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
Expanded(
|
||||||
],
|
flex: 1,
|
||||||
)
|
child: Row(
|
||||||
: Row(
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
IconButton(
|
||||||
children: <Widget>[
|
icon: Icon(Icons.clear),
|
||||||
_buttonOnMenu(
|
onPressed: () => setState(() {
|
||||||
Icon(Icons.fullscreen),
|
_addGroup = false;
|
||||||
() => Navigator.push(
|
}),
|
||||||
context,
|
),
|
||||||
ScaleRoute(
|
IconButton(
|
||||||
page: PodcastDetail(
|
onPressed: () async {
|
||||||
podcastLocal: widget.podcastLocal,
|
print(_selectedGroups);
|
||||||
|
if (_selectedGroups.length > 0) {
|
||||||
|
setState(() {
|
||||||
|
_addGroup = false;
|
||||||
|
});
|
||||||
|
await _groupList.changeGroup(
|
||||||
|
widget.podcastLocal.id,
|
||||||
|
_selectedGroups,
|
||||||
|
);
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Setting Saved',
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
} else
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'At least select one group',
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.done),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: <Widget>[
|
||||||
|
_buttonOnMenu(
|
||||||
|
Icon(Icons.fullscreen),
|
||||||
|
() => Navigator.push(
|
||||||
|
context,
|
||||||
|
ScaleRoute(
|
||||||
|
page: PodcastDetail(
|
||||||
|
podcastLocal: widget.podcastLocal,
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
)),
|
_buttonOnMenu(Icon(Icons.add), () {
|
||||||
_buttonOnMenu(Icon(Icons.add), () {
|
setState(() {
|
||||||
setState(() {
|
_addGroup = true;
|
||||||
_addGroup = true;
|
});
|
||||||
});
|
}),
|
||||||
}),
|
_buttonOnMenu(Icon(Icons.notifications), () {}),
|
||||||
_buttonOnMenu(Icon(Icons.notifications), () {}),
|
_buttonOnMenu(Icon(Icons.remove_circle), () {
|
||||||
_buttonOnMenu(Icon(Icons.remove_circle), () {
|
showDialog(
|
||||||
showDialog(
|
context: context,
|
||||||
context: context,
|
child:
|
||||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
systemNavigationBarColor:
|
systemNavigationBarColor:
|
||||||
Colors.black.withOpacity(0.5),
|
Colors.black.withOpacity(0.5),
|
||||||
statusBarColor: Colors.red,
|
statusBarColor: Colors.red,
|
||||||
),
|
),
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
elevation: 2.0,
|
elevation: 2.0,
|
||||||
title: Text('Remove confirm'),
|
title: Text('Remove confirm'),
|
||||||
content: Text(
|
content: Text(
|
||||||
'${widget.podcastLocal.title} will be removed from device.'),
|
'${widget.podcastLocal.title} will be removed from device.'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
Navigator.of(context).pop(),
|
Navigator.of(context).pop(),
|
||||||
child: Text('CANCEL'),
|
child: Text('CANCEL'),
|
||||||
),
|
|
||||||
FlatButton(
|
|
||||||
onPressed: () {
|
|
||||||
_groupList.removePodcast(
|
|
||||||
widget.podcastLocal.id);
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'CONFIRM',
|
|
||||||
style: TextStyle(color: Colors.red),
|
|
||||||
),
|
),
|
||||||
)
|
FlatButton(
|
||||||
],
|
onPressed: () {
|
||||||
),
|
_groupList.removePodcast(
|
||||||
));
|
widget.podcastLocal.id);
|
||||||
}),
|
Navigator.of(context).pop();
|
||||||
],
|
},
|
||||||
),
|
child: Text(
|
||||||
|
'CONFIRM',
|
||||||
|
style:
|
||||||
|
TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
pubspec.lock
16
pubspec.lock
@ -92,13 +92,6 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.8"
|
version: "3.0.8"
|
||||||
expandable:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: expandable
|
|
||||||
url: "https://pub.flutter-io.cn"
|
|
||||||
source: hosted
|
|
||||||
version: "4.1.2"
|
|
||||||
file_picker:
|
file_picker:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -385,6 +378,13 @@ packages:
|
|||||||
url: "https://pub.flutter-io.cn"
|
url: "https://pub.flutter-io.cn"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.11"
|
version: "0.2.11"
|
||||||
|
tuple:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
url: "https://pub.flutter-io.cn"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -449,5 +449,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.5.0"
|
version: "3.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.7.0 <3.0.0"
|
dart: ">=2.6.0 <3.0.0"
|
||||||
flutter: ">=1.12.13+hotfix.4 <2.0.0"
|
flutter: ">=1.12.13+hotfix.4 <2.0.0"
|
||||||
|
@ -48,7 +48,7 @@ dev_dependencies:
|
|||||||
image: ^2.1.4
|
image: ^2.1.4
|
||||||
shared_preferences: ^0.5.6+1
|
shared_preferences: ^0.5.6+1
|
||||||
uuid: ^2.0.4
|
uuid: ^2.0.4
|
||||||
expandable: ^4.1.2
|
tuple: ^1.0.3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user