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:
stonegate 2020-02-25 17:57:12 +08:00
parent d4ebdf769d
commit 547aef80e9
15 changed files with 1270 additions and 1438 deletions

View File

@ -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');
}

View File

@ -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

View File

@ -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(),
),
);
},
),
],
),
);

View File

@ -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(),

View File

@ -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));
}
}

View File

@ -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();
}

View File

@ -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 {

View File

@ -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(

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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(

View File

@ -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 {

View File

@ -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),
),
)
],
),
));
}),
],
),
),
),
),
],
],
),
);
}
}

View File

@ -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"

View File

@ -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