mirror of
https://github.com/stonega/tsacdop
synced 2025-03-10 00:00:07 +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: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 get episode => _episode;
|
||||
set episodeLoad(EpisodeBrief episdoe){
|
||||
_episode = episdoe;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
AudioState _audioState;
|
||||
Playlist _queue = Playlist('now');
|
||||
bool _playerRunning = false;
|
||||
Audio _backgroundAudio;
|
||||
bool _backgroundAudioPlaying = false;
|
||||
double _backgroundAudioDurationSeconds = 0;
|
||||
double _backgroundAudioPositionSeconds = 0;
|
||||
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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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 get theme => _theme;
|
||||
void setTheme(int theme) {
|
||||
setTheme(int theme) async{
|
||||
_theme = theme;
|
||||
notifyListeners();
|
||||
_saveTheme(theme);
|
||||
await _saveTheme(theme);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -5,9 +5,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
@ -66,116 +68,137 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||
title: Text(widget.episodeItem.feedTitle),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
alignment: Alignment.topLeft,
|
||||
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),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Theme.of(context).primaryColor,
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12.0),
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
widget.episodeItem.title,
|
||||
style: Theme.of(context).textTheme.headline5,
|
||||
),
|
||||
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),
|
||||
),
|
||||
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(
|
||||
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,
|
||||
heroTag: widget.heroTag,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Selector<AudioPlayer, bool>(
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final audioplay = Provider.of<AudioPlay>(context);
|
||||
var audio = Provider.of<AudioPlayer>(context, listen: false);
|
||||
return Container(
|
||||
height: 50.0,
|
||||
decoration: BoxDecoration(
|
||||
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)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
(widget.episodeItem.title == audioplay.episode?.title &&
|
||||
audioplay.audioState == AudioState.play)
|
||||
? ImageRotate(
|
||||
title: widget.episodeItem.feedTitle,
|
||||
path: widget.episodeItem.imagePath,
|
||||
)
|
||||
: Hero(
|
||||
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
child: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child:
|
||||
Image.file(File("${widget.episodeItem.imagePath}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
Hero(
|
||||
tag: widget.episodeItem.enclosureUrl + widget.heroTag,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal:10.0),
|
||||
child: ClipRRect(
|
||||
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)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
@ -273,72 +294,86 @@ class _MenuBarState extends State<MenuBar> {
|
||||
color: Colors.grey[700],
|
||||
),
|
||||
() => saveLiked(widget.episodeItem.enclosureUrl))
|
||||
: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
LoveOpen(),
|
||||
_buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
||||
],
|
||||
),
|
||||
: (_like == 1 && !_liked)
|
||||
? _buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
() => setUnliked(widget.episodeItem.enclosureUrl))
|
||||
: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
LoveOpen(),
|
||||
_buttonOnMenu(
|
||||
Icon(
|
||||
Icons.favorite,
|
||||
color: Colors.red,
|
||||
),
|
||||
() => setUnliked(widget.episodeItem.enclosureUrl)),
|
||||
],
|
||||
),
|
||||
DownloadButton(episodeBrief: widget.episodeItem),
|
||||
_buttonOnMenu(Icon(Icons.playlist_add, color: Colors.grey[700]), () {
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Not support yet',
|
||||
msg: 'Added to playlist',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
/*TODO*/
|
||||
audio.addToPlaylist(widget.episodeItem);
|
||||
}),
|
||||
Spacer(),
|
||||
(widget.episodeItem.title != audioplay.episode?.title)
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(5.0),
|
||||
bottomRight: Radius.circular(5.0)),
|
||||
onTap: () {
|
||||
audioplay.episodeLoad = widget.episodeItem;
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
height: 50.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Text('Play Now',
|
||||
style: TextStyle(
|
||||
// Text(audio.audioState.toString()),
|
||||
Selector<AudioPlayer, Tuple2<EpisodeBrief, bool>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple2(audio.episode, audio.backgroundAudioPlaying),
|
||||
builder: (_, data, __) {
|
||||
return (widget.episodeItem.title != data.item1?.title)
|
||||
? Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(5.0),
|
||||
bottomRight: Radius.circular(5.0)),
|
||||
onTap: () {
|
||||
audio.episodeLoad(widget.episodeItem);
|
||||
},
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
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,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
)),
|
||||
Icon(
|
||||
Icons.play_arrow,
|
||||
color: Theme.of(context).accentColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: (widget.episodeItem.title == audioplay.episode?.title &&
|
||||
audioplay.audioState == AudioState.play)
|
||||
? Container(
|
||||
padding: EdgeInsets.only(right: 30),
|
||||
child:
|
||||
SizedBox(width: 20, height: 15, child: WaveLoader()))
|
||||
: Container(
|
||||
padding: EdgeInsets.only(right: 30),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 15,
|
||||
child: LineLoader(),
|
||||
),
|
||||
),
|
||||
)
|
||||
: (widget.episodeItem.title == data.item1?.title &&
|
||||
data.item2 == true)
|
||||
? Container(
|
||||
padding: EdgeInsets.only(right: 30),
|
||||
child: SizedBox(
|
||||
width: 20, height: 15, child: WaveLoader()))
|
||||
: Container(
|
||||
padding: EdgeInsets.only(right: 30),
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 15,
|
||||
child: LineLoader(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -256,7 +256,7 @@ class _SearchResultState extends State<SearchResult> {
|
||||
String _uuid = Uuid().v4();
|
||||
File("${dir.path}/$_uuid.png")
|
||||
..writeAsBytesSync(img.encodePng(thumbnail));
|
||||
|
||||
|
||||
String _imagePath = "${dir.path}/$_uuid.png";
|
||||
String _primaryColor = await getColor(File("${dir.path}/$_uuid.png"));
|
||||
String _author = _p.itunes.author ?? _p.author ?? '';
|
||||
@ -303,6 +303,11 @@ class _SearchResultState extends State<SearchResult> {
|
||||
}
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -363,18 +368,19 @@ class _SearchResultState extends State<SearchResult> {
|
||||
? Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColorDark,
|
||||
color: Theme.of(context).accentColor,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(15.0),
|
||||
bottomLeft: Radius.circular(15.0),
|
||||
bottomRight: Radius.circular(15.0),
|
||||
)),
|
||||
margin: EdgeInsets.only(left: 70, right: 50),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
margin: EdgeInsets.only(left: 70, right: 50, bottom: 10.0),
|
||||
padding: EdgeInsets.all(15.0),
|
||||
child: Text(
|
||||
widget.onlinePodcast.description.trim(),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyText1.copyWith(color: Colors.white),
|
||||
),
|
||||
)
|
||||
: 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(
|
||||
onTap: () => _backToMini(),
|
||||
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(
|
||||
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: Opacity(
|
||||
opacity: _animation.value < (maxSize - 50)
|
||||
@ -99,7 +110,7 @@ class _AudioPanelState extends State<AudioPanel>
|
||||
setState(() {
|
||||
_animation =
|
||||
Tween<double>(begin: initSize, end: minSize).animate(_controller);
|
||||
initSize = minSize;
|
||||
initSize = minSize;
|
||||
});
|
||||
_controller.forward();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'hometab.dart';
|
||||
import 'package:tsacdop/home/appbar/importompl.dart';
|
||||
import 'package:tsacdop/home/audio_player.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
import 'homescroll.dart';
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
|
@ -263,7 +263,10 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
||||
.map<Widget>((PodcastLocal podcastLocal) {
|
||||
return Container(
|
||||
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),
|
||||
key: ObjectKey(podcastLocal.title),
|
||||
child: PodcastPreview(
|
||||
@ -426,19 +429,23 @@ class ShowEpisode extends StatelessWidget {
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).primaryColor,
|
||||
width: 3.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).primaryColor,
|
||||
blurRadius: 1.0,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
]),
|
||||
borderRadius: BorderRadius.all(Radius.circular(5.0)),
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness == Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context).scaffoldBackgroundColor,
|
||||
// color: Theme.of(context).primaryColor,
|
||||
width: 3.0,
|
||||
),
|
||||
// boxShadow: [
|
||||
// BoxShadow(
|
||||
// color: Theme.of(context).primaryColor,
|
||||
// blurRadius: 1.0,
|
||||
// spreadRadius: 0.5,
|
||||
// ),
|
||||
// ]
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
|
@ -42,4 +42,16 @@ class KeyValueStorage {
|
||||
if(prefs.getInt(key) == null) await prefs.setInt(key, 0);
|
||||
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:dio/dio.dart';
|
||||
import 'package:tsacdop/class/podcastlocal.dart';
|
||||
import 'package:tsacdop/class/audiostate.dart';
|
||||
import 'package:tsacdop/class/episodebrief.dart';
|
||||
import 'package:tsacdop/webfeed/webfeed.dart';
|
||||
|
||||
@ -36,12 +37,14 @@ class DBHelper {
|
||||
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
||||
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
|
||||
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
|
||||
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 {
|
||||
var dbClient = await database;
|
||||
List<PodcastLocal> podcastLocal = List();
|
||||
|
||||
await Future.forEach(podcasts, (s) async {
|
||||
List<Map> list;
|
||||
list = await dbClient.rawQuery(
|
||||
@ -66,7 +69,7 @@ class DBHelper {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT id, title, imageUrl, rssUrl, primaryColor, author, imagePath, email, provider, link FROM PodcastLocal ORDER BY add_date DESC');
|
||||
|
||||
|
||||
List<PodcastLocal> podcastLocal = List();
|
||||
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
@ -78,7 +81,7 @@ class DBHelper {
|
||||
list[i]['author'],
|
||||
list[i]['id'],
|
||||
list[i]['imagePath'],
|
||||
list.first['email'],
|
||||
list.first['email'],
|
||||
list.first['provider'],
|
||||
list.first['link']));
|
||||
}
|
||||
@ -132,6 +135,24 @@ class DBHelper {
|
||||
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) {
|
||||
if (pubDate == null) return DateTime.now();
|
||||
print(pubDate);
|
||||
@ -156,11 +177,13 @@ class DBHelper {
|
||||
if (year != null && time != null && month != null) {
|
||||
date = DateFormat('dd MMM yyyy HH:mm', 'en_US')
|
||||
.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);
|
||||
date = DateFormat('mm-dd yyyy HH:mm', 'en_US')
|
||||
.parse(month +' ' + year +' '+ time);
|
||||
} else {date = DateTime.now();}
|
||||
.parse(month + ' ' + year + ' ' + time);
|
||||
} else {
|
||||
date = DateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -488,8 +511,8 @@ class DBHelper {
|
||||
|
||||
Future<String> getFeedDescription(String id) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
'SELECT description FROM PodcastLocal WHERE id = ?', [id]);
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery('SELECT description FROM PodcastLocal WHERE id = ?', [id]);
|
||||
String description = list[0]['description'];
|
||||
return description;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ void main() async {
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (context) => AudioPlay()),
|
||||
ChangeNotifierProvider(create: (context) => AudioPlayer()),
|
||||
ChangeNotifierProvider(create: (context) => ImportOmpl()),
|
||||
ChangeNotifierProvider(create: (context) => SettingState()),
|
||||
ChangeNotifierProvider(create: (context) => GroupList()),
|
||||
@ -29,10 +29,11 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Provider.of<SettingState>(context).theme;
|
||||
print(theme);
|
||||
return MaterialApp(
|
||||
themeMode: theme == 0
|
||||
? ThemeMode.system
|
||||
: theme == 1 ? ThemeMode.dark : ThemeMode.light,
|
||||
: theme == 1 ? ThemeMode.light: ThemeMode.dark,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'TsacDop',
|
||||
theme: ThemeData(
|
||||
|
@ -15,6 +15,7 @@ import 'package:tsacdop/episodes/episodedetail.dart';
|
||||
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
|
||||
import 'package:tsacdop/util/episodegrid.dart';
|
||||
import 'package:tsacdop/util/pageroute.dart';
|
||||
import 'package:tsacdop/home/audioplayer.dart';
|
||||
|
||||
class PodcastDetail extends StatefulWidget {
|
||||
PodcastDetail({Key key, this.podcastLocal}) : super(key: key);
|
||||
@ -73,11 +74,11 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
double _width = MediaQuery.of(context).size.width;
|
||||
Color _color;
|
||||
var color = json.decode(widget.podcastLocal.primaryColor);
|
||||
(color[0] > 200 && color[1] > 200 && color[2] > 200)
|
||||
? _color = Color.fromRGBO(
|
||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
||||
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
||||
|
||||
(color[0] > 200 && color[1] > 200 && color[2] > 200)
|
||||
? _color = Color.fromRGBO(
|
||||
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0)
|
||||
: _color = Color.fromRGBO(color[0], color[1], color[2], 1.0);
|
||||
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
@ -91,286 +92,317 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
||||
key: _refreshIndicatorKey,
|
||||
color: Theme.of(context).accentColor,
|
||||
onRefresh: () => _updateRssItem(widget.podcastLocal),
|
||||
child: FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
expandedHeight: 170,
|
||||
backgroundColor: _color,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(builder:
|
||||
(BuildContext context,
|
||||
BoxConstraints constraints) {
|
||||
top = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 120),
|
||||
padding: EdgeInsets.only(left: 80, right: 120),
|
||||
color: Colors.white10,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(widget.podcastLocal.author ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.grey[300])),
|
||||
Text(widget.podcastLocal.provider ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Colors.grey[300]))
|
||||
],
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
FutureBuilder<List<EpisodeBrief>>(
|
||||
future: _getRssItem(widget.podcastLocal),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) print(snapshot.error);
|
||||
return (snapshot.hasData)
|
||||
? CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
primary: true,
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
elevation: 0,
|
||||
iconTheme: IconThemeData(color: Colors.white),
|
||||
expandedHeight: 170,
|
||||
backgroundColor: _color,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
flexibleSpace: LayoutBuilder(builder:
|
||||
(BuildContext context,
|
||||
BoxConstraints constraints) {
|
||||
top = constraints.biggest.height;
|
||||
return FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 120),
|
||||
padding: EdgeInsets.only(
|
||||
left: 80, right: 120),
|
||||
color: Colors.white10,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.podcastLocal.author ??
|
||||
'',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.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(
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
title: top < 70
|
||||
? Text(widget.podcastLocal.title,
|
||||
style: TextStyle(color: Colors.white))
|
||||
: Center(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
EpisodeBrief episodeBrief =
|
||||
snapshot.data[index];
|
||||
Color _c;
|
||||
var color = json
|
||||
.decode(widget.podcastLocal.primaryColor);
|
||||
if (Theme.of(context).brightness ==
|
||||
Brightness.light) {
|
||||
(color[0] > 200 &&
|
||||
color[1] > 200 &&
|
||||
color[2] > 200)
|
||||
? _c = Color.fromRGBO((255 - color[0]),
|
||||
255 - color[1], 255 - color[2], 1.0)
|
||||
: _c = Color.fromRGBO(
|
||||
color[0], color[1], color[2], 1.0);
|
||||
} else {
|
||||
(color[0] < 50 &&
|
||||
color[1] < 50 &&
|
||||
color[2] < 50)
|
||||
? _c = Color.fromRGBO((255 - color[0]),
|
||||
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)
|
||||
.scaffoldBackgroundColor,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context).primaryColor
|
||||
: Theme.of(context)
|
||||
.scaffoldBackgroundColor,
|
||||
width: 3.0,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
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(
|
||||
(BuildContext context, int index) {
|
||||
EpisodeBrief episodeBrief =
|
||||
snapshot.data[index];
|
||||
Color _c;
|
||||
var color = json.decode(
|
||||
widget.podcastLocal.primaryColor);
|
||||
if (Theme.of(context).brightness ==
|
||||
Brightness.light) {
|
||||
(color[0] > 200 &&
|
||||
color[1] > 200 &&
|
||||
color[2] > 200)
|
||||
? _c = Color.fromRGBO(
|
||||
(255 - color[0]),
|
||||
255 - color[1],
|
||||
255 - color[2],
|
||||
1.0)
|
||||
: _c = Color.fromRGBO(color[0],
|
||||
color[1], color[2], 1.0);
|
||||
} else {
|
||||
(color[0] < 50 &&
|
||||
color[1] < 50 &&
|
||||
color[2] < 50)
|
||||
? _c = Color.fromRGBO(
|
||||
(255 - color[0]),
|
||||
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)
|
||||
.primaryColor,
|
||||
blurRadius: 0.5,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
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,
|
||||
.scaffoldBackgroundColor,
|
||||
border: Border.all(
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness.light
|
||||
? Theme.of(context)
|
||||
.primaryColor
|
||||
: Theme.of(context)
|
||||
.scaffoldBackgroundColor,
|
||||
width: 3.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context)
|
||||
.primaryColor,
|
||||
blurRadius: 0.5,
|
||||
spreadRadius: 0.5,
|
||||
),
|
||||
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,
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
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
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
_description,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Icon(Icons.keyboard_arrow_down,),
|
||||
),
|
||||
],
|
||||
)
|
||||
Container(
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(_description),
|
||||
);
|
||||
} else {
|
||||
|
@ -165,213 +165,223 @@ class _PodcastCardState extends State<PodcastCard> {
|
||||
var _groupList = Provider.of<GroupList>(context);
|
||||
_belongGroups = _groupList.getPodcastGroup(widget.podcastLocal.id);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () => setState(() => _loadMenu = !_loadMenu),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Icon(
|
||||
Icons.unfold_more,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Image.file(
|
||||
File("${widget.podcastLocal.imagePath}")),
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: Divider.createBorderSide(context),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () => setState(() => _loadMenu = !_loadMenu),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
child: Icon(
|
||||
Icons.unfold_more,
|
||||
color: _c,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: _width / 2,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
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(
|
||||
Container(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.all(Radius.circular(30)),
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Image.file(
|
||||
File("${widget.podcastLocal.imagePath}")),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: _width / 2,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
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()),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
widget.podcastLocal.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold, fontSize: 15),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.clear),
|
||||
onPressed: () => setState(() {
|
||||
_addGroup = false;
|
||||
}),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
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(
|
||||
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,
|
||||
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()),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
_buttonOnMenu(
|
||||
Icon(Icons.fullscreen),
|
||||
() => Navigator.push(
|
||||
context,
|
||||
ScaleRoute(
|
||||
page: PodcastDetail(
|
||||
podcastLocal: widget.podcastLocal,
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.clear),
|
||||
onPressed: () => setState(() {
|
||||
_addGroup = false;
|
||||
}),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
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), () {
|
||||
setState(() {
|
||||
_addGroup = true;
|
||||
});
|
||||
}),
|
||||
_buttonOnMenu(Icon(Icons.notifications), () {}),
|
||||
_buttonOnMenu(Icon(Icons.remove_circle), () {
|
||||
showDialog(
|
||||
context: context,
|
||||
child: AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
Colors.black.withOpacity(0.5),
|
||||
statusBarColor: Colors.red,
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 2.0,
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'${widget.podcastLocal.title} will be removed from device.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
child: Text('CANCEL'),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
_groupList.removePodcast(
|
||||
widget.podcastLocal.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
_buttonOnMenu(Icon(Icons.add), () {
|
||||
setState(() {
|
||||
_addGroup = true;
|
||||
});
|
||||
}),
|
||||
_buttonOnMenu(Icon(Icons.notifications), () {}),
|
||||
_buttonOnMenu(Icon(Icons.remove_circle), () {
|
||||
showDialog(
|
||||
context: context,
|
||||
child:
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
systemNavigationBarColor:
|
||||
Colors.black.withOpacity(0.5),
|
||||
statusBarColor: Colors.red,
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 2.0,
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'${widget.podcastLocal.title} will be removed from device.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
child: Text('CANCEL'),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}),
|
||||
],
|
||||
),
|
||||
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"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -385,6 +378,13 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -449,5 +449,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.5.0"
|
||||
sdks:
|
||||
dart: ">=2.7.0 <3.0.0"
|
||||
dart: ">=2.6.0 <3.0.0"
|
||||
flutter: ">=1.12.13+hotfix.4 <2.0.0"
|
||||
|
@ -48,7 +48,7 @@ dev_dependencies:
|
||||
image: ^2.1.4
|
||||
shared_preferences: ^0.5.6+1
|
||||
uuid: ^2.0.4
|
||||
expandable: ^4.1.2
|
||||
tuple: ^1.0.3
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user