1
0
mirror of https://github.com/stonega/tsacdop synced 2025-02-10 08:30:45 +01:00

new file: lib/class/podcast_group.dart

modified:   lib/class/podcastlocal.dart
	modified:   lib/class/settingstate.dart
	modified:   lib/episodes/episodedetail.dart
	modified:   lib/episodes/episodedownload.dart
	modified:   lib/home/appbar/addpodcast.dart
	modified:   lib/home/appbar/popupmenu.dart
	modified:   lib/home/audio_player.dart
	modified:   lib/home/home.dart
	modified:   lib/home/homescroll.dart
	modified:   lib/home/hometab.dart
	new file:   lib/local_storage/key_value_storage.dart
	renamed:    lib/class/sqflite_localpodcast.dart -> lib/local_storage/sqflite_localpodcast.dart
	modified:   lib/main.dart
	modified:   lib/podcasts/podcastdetail.dart
	new file:   lib/podcasts/podcastgroup.dart
	modified:   lib/podcasts/podcastlist.dart
	modified:   lib/podcasts/podcastmanage.dart
	modified:   lib/util/episodegrid.dart
	modified:   lib/webfeed/domain/rss_item.dart
	modified:   lib/webfeed/domain/rss_itunes.dart
	modified:   pubspec.lock
	modified:   pubspec.yaml
Add podcast group support
Manage group with provider
This commit is contained in:
stonegate 2020-02-20 17:09:21 +08:00
parent d3efce463c
commit 16567a7199
23 changed files with 1275 additions and 524 deletions

View File

@ -0,0 +1,158 @@
import 'dart:collection';
import 'dart:core';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:tsacdop/local_storage/key_value_storage.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:uuid/uuid.dart';
import 'package:tsacdop/class/podcastlocal.dart';
class GroupEntity {
final String name;
final String id;
final String color;
final List<String> podcastList;
GroupEntity(this.name, this.id, this.color, this.podcastList);
Map<String, Object> toJson() {
return {'name': name, 'id': id, 'color': color, 'podcastList': podcastList};
}
static GroupEntity fromJson(Map<String, Object> json) {
List<String> list = List.from(json['podcastList']);
print(json['[podcastList']);
return
GroupEntity(
json['name'] as String,
json['id'] as String,
json['color'] as String,
list);
}
}
class PodcastGroup {
final String name;
final String id;
final String color;
final List<String> podcastList;
PodcastGroup(this.name,
{this.color = '#000000', String id, List<String> podcastList})
: id = id ?? Uuid().v4(),
podcastList = podcastList ?? [];
Future getPodcasts() async {
var dbHelper = DBHelper();
if (podcastList != []) {
_podcasts = await dbHelper.getPodcastLocal(podcastList, 0);
}
}
List<PodcastLocal> _podcasts;
List<PodcastLocal> get podcasts => _podcasts;
GroupEntity toEntity() {
return GroupEntity(name, id, color, podcastList);
}
static PodcastGroup fromEntity(GroupEntity entity) {
return PodcastGroup(
entity.name,
id: entity.id,
color: entity.color,
podcastList: entity.podcastList,
);
}
}
class GroupList extends ChangeNotifier {
List<PodcastGroup> _groups;
DBHelper dbHelper = DBHelper();
UnmodifiableListView<PodcastGroup> get groups =>
UnmodifiableListView(_groups);
KeyValueStorage storage = KeyValueStorage('groups');
GroupList({List<PodcastGroup> groups}) : _groups = groups ?? [];
bool _isLoading = false;
bool get isLoading => _isLoading;
@override
void addListener(VoidCallback listener) {
super.addListener(listener);
loadGroups();
}
Future loadGroups() async {
_isLoading = true;
notifyListeners();
storage.getGroups().then((loadgroups) async {
_groups.addAll(loadgroups.map((e) => PodcastGroup.fromEntity(e)));
await Future.forEach(_groups, (group) async {
await group.getPodcasts();
});
_isLoading = false;
notifyListeners();
});
}
Future addGroup(PodcastGroup podcastGroup) async {
_groups.add(podcastGroup);
_saveGroup();
notifyListeners();
}
Future delGroup(PodcastGroup podcastGroup) async {
_groups.remove(podcastGroup);
notifyListeners();
_saveGroup();
}
void updateGroup(PodcastGroup podcastGroup) {
var oldGroup = _groups.firstWhere((it) => it.id == podcastGroup.id);
var index = _groups.indexOf(oldGroup);
_groups.replaceRange(index, index + 1, [podcastGroup]);
notifyListeners();
_saveGroup();
}
void _saveGroup() {
storage.saveGroup(_groups.map((it) => it.toEntity()).toList());
}
Future subscribe(PodcastLocal podcastLocal) async {
_groups[0].podcastList.add(podcastLocal.id);
_saveGroup();
await dbHelper.savePodcastLocal(podcastLocal);
await _groups[0].getPodcasts();
notifyListeners();
}
List<PodcastGroup> getPodcastGroup(String id) {
List<PodcastGroup> result =[];
_groups.forEach((group) {
if (group.podcastList.contains(id)) {
result.add(group);
}
});
return result;
}
changeGroup(String id, List<String> l) {
_groups.forEach((group) {
if (group.podcastList.contains(id)) {
group.podcastList.remove(id);
}
});
l.forEach((s) {
_groups.forEach((group) {
if (group.name == s) group.podcastList.add(id);
});
});
notifyListeners();
_saveGroup();
}
}

View File

@ -1,4 +1,6 @@
import 'package:uuid/uuid.dart';
class PodcastLocal { class PodcastLocal {
final String title; final String title;
final String imageUrl; final String imageUrl;
@ -6,5 +8,7 @@ class PodcastLocal {
final String author; final String author;
String description; String description;
final String primaryColor; final String primaryColor;
PodcastLocal(this.title, this.imageUrl, this.rssUrl, this.primaryColor, this.author); final String id;
PodcastLocal(this.title, this.imageUrl, this.rssUrl, this.primaryColor, this.author,{String id}) : id = id ?? Uuid().v4();
} }

View File

@ -1,19 +1,13 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
enum Setting {start, stop} //two types podcast update, backhome nedd to back to default grooup.
enum Update {backhome, justupdate}
class SettingState extends ChangeNotifier{ class SettingState extends ChangeNotifier{
Setting _subscribeupdate; Update _subscribeupdate;
Setting get subscribeupdate => _subscribeupdate; Update get subscribeupdate => _subscribeupdate;
set subscribeUpdate(Setting s){ set subscribeUpdate(Update s){
_subscribeupdate = s; _subscribeupdate = s;
notifyListeners(); notifyListeners();
} }
Setting _themeupdate;
Setting get themeUpdate => _themeupdate;
set themeUpdate(Setting s){
_themeupdate = s;
notifyListeners();
}
} }

View File

@ -1,22 +1,23 @@
import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:path_provider/path_provider.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'episodedownload.dart'; import 'episodedownload.dart';
enum DownloadState { stop, load, donwload, complete, error }
class EpisodeDetail extends StatefulWidget { class EpisodeDetail extends StatefulWidget {
final EpisodeBrief episodeItem; final EpisodeBrief episodeItem;
final String heroTag; final String heroTag;
EpisodeDetail({this.episodeItem, this.heroTag, Key key}) : super(key: key); final String path;
EpisodeDetail({this.episodeItem, this.heroTag, this.path, Key key})
: super(key: key);
@override @override
_EpisodeDetailState createState() => _EpisodeDetailState(); _EpisodeDetailState createState() => _EpisodeDetailState();
@ -26,7 +27,10 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
final textstyle = TextStyle(fontSize: 15.0, color: Colors.black); final textstyle = TextStyle(fontSize: 15.0, color: Colors.black);
double downloadProgress; double downloadProgress;
bool _loaddes; bool _loaddes;
String path;
Future getSDescription(String title) async { Future getSDescription(String title) async {
var dir = await getApplicationDocumentsDirectory();
path = dir.path;
var dbHelper = DBHelper(); var dbHelper = DBHelper();
widget.episodeItem.description = await dbHelper.getDescription(title); widget.episodeItem.description = await dbHelper.getDescription(title);
if (mounted) if (mounted)
@ -62,7 +66,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
), ),
body: Container( body: Container(
color: Colors.grey[100], color: Colors.grey[100],
padding: EdgeInsets.all(12.0), padding: EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -77,7 +81,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Text( child: Text(
widget.episodeItem.title, widget.episodeItem.title,
style: Theme.of(context).textTheme.title, style: Theme.of(context).textTheme.headline5,
), ),
), ),
Container( Container(
@ -144,22 +148,26 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
child: Container( child: Container(
padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0), padding: EdgeInsets.only(left: 12.0, right: 12.0, top: 5.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: (widget.episodeItem.description != null && _loaddes) child: _loaddes
? Html( ? (widget.episodeItem.description.contains('<'))
data: widget.episodeItem.description, ? Html(
onLinkTap: (url) { data: widget.episodeItem.description,
_launchUrl(url); onLinkTap: (url) {
}, _launchUrl(url);
useRichText: true, },
) useRichText: true,
)
: Container(
alignment: Alignment.topLeft,
child: Text(widget.episodeItem.description))
: Center(), : Center(),
), ),
), ),
), ),
MenuBar( MenuBar(
episodeItem: widget.episodeItem, episodeItem: widget.episodeItem,
heroTag: widget.heroTag, heroTag: widget.heroTag,
), path: widget.path),
], ],
), ),
), ),
@ -168,9 +176,11 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
} }
class MenuBar extends StatefulWidget { class MenuBar extends StatefulWidget {
final String path;
final EpisodeBrief episodeItem; final EpisodeBrief episodeItem;
final String heroTag; final String heroTag;
MenuBar({this.episodeItem, this.heroTag, Key key}) : super(key: key); MenuBar({this.episodeItem, this.heroTag, this.path, Key key})
: super(key: key);
@override @override
_MenuBarState createState() => _MenuBarState(); _MenuBarState createState() => _MenuBarState();
} }
@ -204,7 +214,7 @@ class _MenuBarState extends State<MenuBar> {
_like = widget.episodeItem.liked; _like = widget.episodeItem.liked;
} }
Widget _buttonOnMenu(Widget widget, Function() onTap) => Material( Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
@ -232,7 +242,8 @@ class _MenuBarState extends State<MenuBar> {
(widget.episodeItem.title == urlChange.title && (widget.episodeItem.title == urlChange.title &&
urlChange.audioState == AudioState.play) urlChange.audioState == AudioState.play)
? ImageRotate( ? ImageRotate(
url: widget.episodeItem.imageUrl, title: widget.episodeItem.feedTitle,
path: widget.path,
) )
: Hero( : Hero(
tag: widget.episodeItem.enclosureUrl + widget.heroTag, tag: widget.episodeItem.enclosureUrl + widget.heroTag,
@ -244,9 +255,8 @@ class _MenuBarState extends State<MenuBar> {
height: 30.0, height: 30.0,
width: 30.0, width: 30.0,
color: Colors.white, color: Colors.white,
child: CachedNetworkImage( child: Image.file(File(
imageUrl: widget.episodeItem.imageUrl, "${widget.path}/${widget.episodeItem.feedTitle}.png")),
),
), ),
), ),
), ),
@ -298,10 +308,18 @@ class _MenuBarState extends State<MenuBar> {
alignment: Alignment.center, alignment: Alignment.center,
height: 50.0, height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 20.0), padding: EdgeInsets.symmetric(horizontal: 20.0),
child:Row( child: Row(
children: <Widget>[ children: <Widget>[
Text('Play Now', style: TextStyle(color: Colors.blue, fontSize: 15, fontWeight: FontWeight.bold,)), Text('Play Now',
Icon(Icons.play_arrow, color: Colors.blue,), style: TextStyle(
color: Colors.blue,
fontSize: 15,
fontWeight: FontWeight.bold,
)),
Icon(
Icons.play_arrow,
color: Colors.blue,
),
], ],
), ),
), ),
@ -487,8 +505,9 @@ class _WaveLoaderState extends State<WaveLoader>
} }
class ImageRotate extends StatefulWidget { class ImageRotate extends StatefulWidget {
final String url; final String title;
ImageRotate({this.url, Key key}) : super(key: key); final String path;
ImageRotate({this.title, this.path, Key key}) : super(key: key);
@override @override
_ImageRotateState createState() => _ImageRotateState(); _ImageRotateState createState() => _ImageRotateState();
} }
@ -541,9 +560,7 @@ class _ImageRotateState extends State<ImageRotate>
height: 30.0, height: 30.0,
width: 30.0, width: 30.0,
color: Colors.white, color: Colors.white,
child: CachedNetworkImage( child: Image.file(File("${widget.path}/${widget.title}.png")),
imageUrl: widget.url,
),
), ),
), ),
), ),
@ -590,29 +607,20 @@ class LoveOpen extends StatefulWidget {
class _LoveOpenState extends State<LoveOpen> class _LoveOpenState extends State<LoveOpen>
with SingleTickerProviderStateMixin { with SingleTickerProviderStateMixin {
Animation _animationA; Animation _animationA;
Animation _animationB;
AnimationController _controller; AnimationController _controller;
var rect = RelativeRect.fromLTRB(100, 100, 100, 100);
var rectend = RelativeRect.fromLTRB(0, 0, 0, 0);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controller = AnimationController( _controller = AnimationController(
vsync: this, vsync: this,
duration: Duration(milliseconds: 500), duration: Duration(milliseconds: 100),
); );
_animationA = Tween(begin: 0.0, end: 1.0).animate(_controller) _animationA = Tween(begin: 0.0, end: 1.0).animate(_controller)
..addListener(() { ..addListener(() {
if (mounted) setState(() {}); if (mounted) setState(() {});
}); });
_animationB =
RelativeRectTween(begin: rect, end: rectend).animate(_controller)
..addListener(() {
if (mounted) setState(() {});
});
_controller.forward(); _controller.forward();
_controller.addStatusListener((status) { _controller.addStatusListener((status) {
@ -635,8 +643,8 @@ class _LoveOpenState extends State<LoveOpen>
scale: _animationA, scale: _animationA,
alignment: Alignment.center, alignment: Alignment.center,
child: Transform.rotate( child: Transform.rotate(
angle: angle, angle: angle,
child: SizedBox( child: SizedBox(
height: 5 * scale, height: 5 * scale,
width: 6 * scale, width: 6 * scale,
child: CustomPaint( child: CustomPaint(
@ -658,22 +666,22 @@ class _LoveOpenState extends State<LoveOpen>
children: <Widget>[ children: <Widget>[
Row( Row(
children: <Widget>[ children: <Widget>[
_littleHeart(1.3, 10, -math.pi/6), _littleHeart(0.5, 10, -math.pi / 6),
_littleHeart(1.5, 3, 0), _littleHeart(1.2, 3, 0),
], ],
), ),
Row( Row(
children: <Widget>[ children: <Widget>[
_littleHeart(0.8, 6, math.pi*1.5), _littleHeart(0.8, 6, math.pi * 1.5),
_littleHeart(1.2, 24, math.pi/2), _littleHeart(0.9, 24, math.pi / 2),
], ],
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[ children: <Widget>[
_littleHeart(1, 8,-math.pi*0.7), _littleHeart(1, 8, -math.pi * 0.7),
_littleHeart(1.3, 8, math.pi), _littleHeart(0.8, 8, math.pi),
_littleHeart(1.1, 3, -math.pi*1.2) _littleHeart(0.6, 3, -math.pi * 1.2)
], ],
), ),
], ],

View File

@ -8,8 +8,9 @@ import 'package:path_provider/path_provider.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/downloadstate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
class DownloadButton extends StatefulWidget { class DownloadButton extends StatefulWidget {
final EpisodeBrief episodeBrief; final EpisodeBrief episodeBrief;
@ -150,9 +151,9 @@ class _DownloadButtonState extends State<DownloadButton> {
final tasks = await FlutterDownloader.loadTasks(); final tasks = await FlutterDownloader.loadTasks();
_task = _TaskInfo( _task = _TaskInfo(
name: widget.episodeBrief.title, name: widget.episodeBrief.title,
link: widget.episodeBrief.enclosureUrl); link: widget.episodeBrief.enclosureUrl,
);
tasks?.forEach((task) { tasks?.forEach((task) {
if (_task.link == task.url) { if (_task.link == task.url) {
_task.taskId = task.taskId; _task.taskId = task.taskId;
@ -203,13 +204,28 @@ class _DownloadButtonState extends State<DownloadButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _downloadButton(_task); return _isLoading
? Center()
: Row(
children: <Widget>[
_downloadButton(_task),
AnimatedContainer(
duration: Duration(seconds: 1),
decoration: BoxDecoration(
color: Colors.cyan[300],
borderRadius: BorderRadius.all(Radius.circular(15.0))),
height: 20.0,
width:
(_task.status == DownloadTaskStatus.running) ? 50.0 : 0,
alignment: Alignment.center,
child: Text('${_task.progress}%',
style: TextStyle(color: Colors.white))),
],
);
} }
Widget _downloadButton(_TaskInfo task) { Widget _downloadButton(_TaskInfo task) {
if (_isLoading) if (task.status == DownloadTaskStatus.undefined) {
return Center();
else if (task.status == DownloadTaskStatus.undefined) {
return _buttonOnMenu( return _buttonOnMenu(
Icon( Icon(
Icons.arrow_downward, Icons.arrow_downward,
@ -217,33 +233,28 @@ class _DownloadButtonState extends State<DownloadButton> {
), ),
() => _requestDownload(task)); () => _requestDownload(task));
} else if (task.status == DownloadTaskStatus.running) { } else if (task.status == DownloadTaskStatus.running) {
return Row( return Material(
children: <Widget>[ color: Colors.transparent,
Material( child: InkWell(
color: Colors.transparent, onTap: () {
child: InkWell( (task.progress > 0) ? _pauseDownload(task) : null;
onTap: () { },
_pauseDownload(task); child: Container(
}, height: 50.0,
child: Container( alignment: Alignment.center,
height: 50.0, padding: EdgeInsets.symmetric(horizontal: 18.0),
alignment: Alignment.center, child: SizedBox(
padding: EdgeInsets.symmetric(horizontal: 18.0), height: 18,
child: SizedBox( width: 18,
height: 18, child: CircularProgressIndicator(
width: 18, backgroundColor: Colors.grey[500],
child: CircularProgressIndicator( strokeWidth: 2,
backgroundColor: Colors.grey[500], valueColor: AlwaysStoppedAnimation<Color>(Colors.cyan[300]),
strokeWidth: 2, value: task.progress / 100,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
value: task.progress / 100,
),
),
), ),
), ),
), ),
Text('${task.progress}%', style: TextStyle(color: Colors.blue,),), ),
],
); );
} else if (task.status == DownloadTaskStatus.paused) { } else if (task.status == DownloadTaskStatus.paused) {
return Material( return Material(

View File

@ -11,9 +11,11 @@ import 'package:image/image.dart' as img;
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/searchpodcast.dart'; import 'package:tsacdop/class/searchpodcast.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/class/settingstate.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/home/home.dart'; import 'package:tsacdop/home/home.dart';
import 'package:tsacdop/home/appbar/popupmenu.dart'; import 'package:tsacdop/home/appbar/popupmenu.dart';
import 'package:tsacdop/webfeed/webfeed.dart'; import 'package:tsacdop/webfeed/webfeed.dart';
@ -211,7 +213,8 @@ class _SearchResultState extends State<SearchResult> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context); ImportOmpl importOmpl = Provider.of<ImportOmpl>(context);
var groupList = Provider.of<GroupList>(context);
var _settingState = Provider.of<SettingState>(context);
savePodcast(String rss) async { savePodcast(String rss) async {
print(rss); print(rss);
if (mounted) setState(() => _adding = true); if (mounted) setState(() => _adding = true);
@ -240,17 +243,19 @@ class _SearchResultState extends State<SearchResult> {
PodcastLocal podcastLocal = PodcastLocal( PodcastLocal podcastLocal = PodcastLocal(
_p.title, _p.itunes.image.href, rss, _primaryColor, _p.author); _p.title, _p.itunes.image.href, rss, _primaryColor, _p.author);
podcastLocal.description = _p.description; podcastLocal.description = _p.description;
var dbHelper = DBHelper(); groupList.subscribe(podcastLocal);
await dbHelper.savePodcastLocal(podcastLocal);
importOmpl.importState = ImportState.parse; importOmpl.importState = ImportState.parse;
var dbHelper = DBHelper();
await dbHelper.savePodcastRss(_p); await dbHelper.savePodcastRss(_p);
importOmpl.importState = ImportState.complete; importOmpl.importState = ImportState.complete;
importOmpl.importState = ImportState.stop;
_settingState.subscribeUpdate = Update.backhome;
print('fatch data'); print('fatch data');
} catch (e) { } catch (e) {
importOmpl.importState = ImportState.error;
Fluttertoast.showToast( Fluttertoast.showToast(
msg: 'Network error, Subscribe failed', msg: 'Network error, Subscribe failed',
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,

View File

@ -4,6 +4,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/class/settingstate.dart';
import 'package:xml/xml.dart' as xml; import 'package:xml/xml.dart' as xml;
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -13,7 +14,7 @@ import 'package:image/image.dart' as img;
import 'about.dart'; import 'about.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/webfeed/webfeed.dart'; import 'package:tsacdop/webfeed/webfeed.dart';
@ -43,10 +44,11 @@ class PopupMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ImportOmpl importOmpl = Provider.of<ImportOmpl>(context); ImportOmpl importOmpl = Provider.of<ImportOmpl>(context);
var _settingState = Provider.of<SettingState>(context);
_refreshAll() async { _refreshAll() async {
var dbHelper = DBHelper(); var dbHelper = DBHelper();
List<PodcastLocal> podcastList = await dbHelper.getPodcastLocal(); List<PodcastLocal> podcastList =
await dbHelper.getPodcastLocalAll();
await Future.forEach(podcastList, (podcastLocal) async { await Future.forEach(podcastList, (podcastLocal) async {
importOmpl.rssTitle = podcastLocal.title; importOmpl.rssTitle = podcastLocal.title;
importOmpl.importState = ImportState.parse; importOmpl.importState = ImportState.parse;
@ -83,12 +85,16 @@ class PopupMenu extends StatelessWidget {
_p.title, _p.itunes.image.href, rss, _primaryColor, _p.author); _p.title, _p.itunes.image.href, rss, _primaryColor, _p.author);
podcastLocal.description = _p.description; podcastLocal.description = _p.description;
print('_p.description');
await dbHelper.savePodcastLocal(podcastLocal); await dbHelper.savePodcastLocal(podcastLocal);
importOmpl.importState = ImportState.parse; importOmpl.importState = ImportState.parse;
await dbHelper.savePodcastRss(_p); await dbHelper.savePodcastRss(_p);
importOmpl.importState = ImportState.complete;
_settingState.subscribeUpdate = Update.backhome;
} catch (e) { } catch (e) {
importOmpl.importState = ImportState.error; importOmpl.importState = ImportState.error;
} }
@ -110,8 +116,6 @@ class PopupMenu extends StatelessWidget {
print(total[i].text); print(total[i].text);
} }
} }
importOmpl.importState = ImportState.complete;
importOmpl.importState = ImportState.stop;
print('Import fisnished'); print('Import fisnished');
} catch (e) { } catch (e) {
importOmpl.importState = ImportState.error; importOmpl.importState = ImportState.error;

View File

@ -15,7 +15,7 @@ import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/home/audiopanel.dart'; import 'package:tsacdop/home/audiopanel.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
final Logger _logger = Logger('audiofileplayer'); final Logger _logger = Logger('audiofileplayer');
@ -84,7 +84,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
_remoteAudioLoading = true; _remoteAudioLoading = true;
Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load; Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) _backgroundAudio?.pause(); if (_backgroundAudioPlaying == true)
{ _backgroundAudio?.pause();
AudioSystem.instance.stopBackgroundDisplay();}
_backgroundAudio?.dispose(); _backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromRemoteUrl(url, _backgroundAudio = Audio.loadFromRemoteUrl(url,
onDuration: (double durationSeconds) { onDuration: (double durationSeconds) {
@ -135,7 +137,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
_remoteAudioLoading = true; _remoteAudioLoading = true;
ByteData audio = getAudio(path); ByteData audio = getAudio(path);
Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load; Provider.of<Urlchange>(context, listen: false).audioState = AudioState.load;
if (_backgroundAudioPlaying == true) _backgroundAudio?.pause(); if (_backgroundAudioPlaying == true)
{_backgroundAudio?.pause();
AudioSystem.instance.stopBackgroundDisplay();}
_backgroundAudio?.dispose(); _backgroundAudio?.dispose();
_backgroundAudio = Audio.loadFromByteData(audio, _backgroundAudio = Audio.loadFromByteData(audio,
onDuration: (double durationSeconds) { onDuration: (double durationSeconds) {

View File

@ -1,13 +1,10 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tsacdop/podcasts/podcastlist.dart';
import 'hometab.dart'; import 'hometab.dart';
import 'package:tsacdop/home/appbar/importompl.dart'; import 'package:tsacdop/home/appbar/importompl.dart';
import 'package:tsacdop/home/audio_player.dart'; import 'package:tsacdop/home/audio_player.dart';
import 'homescroll.dart'; import 'homescroll.dart';
import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/podcasts/podcastmanage.dart';
class Home extends StatelessWidget { class Home extends StatelessWidget {
@ -19,28 +16,6 @@ class Home extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Import(), Import(),
Container(
height: 30,
padding: EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
child: GestureDetector(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(page: PodcastManage()),
);
},
child: Container(
height: 30,
padding: EdgeInsets.all(5.0),
child: Text('See All',
style: TextStyle(
color: Colors.red[300],
fontWeight: FontWeight.bold,
)),
),
),
),
Container(child: ScrollPodcasts()), Container(child: ScrollPodcasts()),
Expanded( Expanded(
child: MainTab(), child: MainTab(),

View File

@ -6,15 +6,17 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/episodes/episodedetail.dart'; import 'package:tsacdop/episodes/episodedetail.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart'; import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/podcasts/podcastlist.dart'; import 'package:tsacdop/podcasts/podcastmanage.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/class/settingstate.dart'; import 'package:tsacdop/class/settingstate.dart';
@ -25,101 +27,184 @@ class ScrollPodcasts extends StatefulWidget {
class _ScrollPodcastsState extends State<ScrollPodcasts> { class _ScrollPodcastsState extends State<ScrollPodcasts> {
var dir; var dir;
bool _loading; int _groupIndex;
List<PodcastLocal> podcastList; bool _loaded;
getPodcastLocal() async {
var dbHelper = DBHelper();
podcastList = await dbHelper.getPodcastLocal();
dir = await getApplicationDocumentsDirectory();
setState(() {
_loading = true;
});
}
ImportState importState; ImportState importState;
Setting subscribeUpdate; Update subscribeUpdate;
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
final importState = Provider.of<ImportOmpl>(context).importState; subscribeUpdate = Provider.of<SettingState>(context).subscribeupdate;
final subscribeUpdate = Provider.of<SettingState>(context).subscribeupdate; if (subscribeUpdate == Update.backhome) {
if (importState == ImportState.complete ||
subscribeUpdate == Setting.start) {
setState(() { setState(() {
getPodcastLocal(); _groupIndex = 0;
}); });
} else if (subscribeUpdate == Update.justupdate) {
setState(() {});
} }
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loading = false; _loaded = false;
getPodcastLocal(); _groupIndex = 0;
getApplicationDocumentsDirectory().then((value) {
dir = value.path;
setState(() => _loaded = true);
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
return !_loading return Consumer<GroupList>(builder: (_, groupList, __) {
? Container( var groups = groupList.groups;
height: (_width - 20) / 3 + 110, bool isLoading = groupList.isLoading;
) return isLoading
: DefaultTabController( ? Container(
length: podcastList.length, height: (_width - 20) / 3 + 110,
child: Column( child: SizedBox(
mainAxisAlignment: MainAxisAlignment.start, height: 20.0,
mainAxisSize: MainAxisSize.min, width: 20.0,
children: <Widget>[ child: CircularProgressIndicator()),
Container( )
height: 70, : DefaultTabController(
alignment: Alignment.centerLeft, length: groups[_groupIndex].podcastList.length,
child: TabBar( child: Column(
labelPadding: EdgeInsets.only( mainAxisAlignment: MainAxisAlignment.start,
top: 5.0, bottom: 10.0, left: 6.0, right: 6.0), mainAxisSize: MainAxisSize.min,
indicator: children: <Widget>[
CircleTabIndicator(color: Colors.blue, radius: 3), GestureDetector(
isScrollable: true, onVerticalDragEnd: (event) {
tabs: podcastList.map<Tab>((PodcastLocal podcastLocal) { if (event.primaryVelocity > 200) {
return Tab( if (groups.length == 1) {
child: ClipRRect( Fluttertoast.showToast(
borderRadius: BorderRadius.all(Radius.circular(25.0)), msg: 'Add some groups',
child: LimitedBox( gravity: ToastGravity.BOTTOM,
maxHeight: 50, );
maxWidth: 50, } else {
child: Image.file( if (mounted)
File("${dir.path}/${podcastLocal.title}.png")), setState(() {
(_groupIndex != 0)
? _groupIndex--
: _groupIndex = groups.length - 1;
});
}
} else if (event.primaryVelocity < -200) {
if (groups.length == 1) {
Fluttertoast.showToast(
msg: 'Add some groups',
gravity: ToastGravity.BOTTOM,
);
} else {
setState(() {
(_groupIndex < groups.length - 1)
? _groupIndex++
: _groupIndex = 0;
});
}
}
},
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Container(
padding:
EdgeInsets.symmetric(horizontal: 15.0),
child: Text(
groups[_groupIndex].name,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red[300]),
)),
Spacer(),
Container(
height: 30,
padding: EdgeInsets.symmetric(horizontal: 15),
alignment: Alignment.bottomRight,
child: InkWell(
onTap: () {
Navigator.push(
context,
SlideLeftRoute(page: PodcastManage()),
);
},
child: Container(
height: 30,
padding: EdgeInsets.all(5.0),
child: Text('See All',
style: TextStyle(
color: Colors.red[300],
fontWeight: FontWeight.bold,
)),
),
),
),
],
), ),
), ),
); Container(
}).toList(), color: Colors.white10,
), height: 70,
), width: _width,
Container( alignment: Alignment.centerLeft,
height: (_width - 20) / 3 + 40, child: TabBar(
margin: EdgeInsets.only(left: 10, right: 10), labelPadding: EdgeInsets.only(
decoration: BoxDecoration( top: 5.0, bottom: 10.0, left: 6.0, right: 6.0),
color: Colors.white, indicator: CircleTabIndicator(
), color: Colors.blue, radius: 3),
child: TabBarView( isScrollable: true,
children: tabs: groups[_groupIndex].podcasts
podcastList.map<Widget>((PodcastLocal podcastLocal) { .map<Tab>((PodcastLocal podcastLocal) {
return Container( return Tab(
decoration: BoxDecoration(color: Colors.grey[100]), child: ClipRRect(
margin: EdgeInsets.symmetric(horizontal: 5.0), borderRadius:
key: ObjectKey(podcastLocal.title), BorderRadius.all(Radius.circular(25.0)),
child: PodcastPreview( child: LimitedBox(
podcastLocal: podcastLocal, maxHeight: 50,
maxWidth: 50,
child: !_loaded
? CircularProgressIndicator()
: Image.file(File(
"$dir/${podcastLocal.title}.png")),
),
),
);
}).toList(),
),
), ),
); ],
}).toList(), ),
), ),
), Container(
], height: (_width - 20) / 3 + 40,
), margin: EdgeInsets.only(left: 10, right: 10),
); decoration: BoxDecoration(
color: Colors.white,
),
child: TabBarView(
children: groups[_groupIndex].podcasts
.map<Widget>((PodcastLocal podcastLocal) {
return Container(
decoration: BoxDecoration(color: Colors.grey[100]),
margin: EdgeInsets.symmetric(horizontal: 5.0),
key: ObjectKey(podcastLocal.title),
child: PodcastPreview(
podcastLocal: podcastLocal,
),
);
}).toList(),
),
),
],
),
);
});
} }
} }
@ -250,6 +335,7 @@ class ShowEpisode extends StatelessWidget {
episodeItem: podcast[index], episodeItem: podcast[index],
heroTag: 'scroll', heroTag: 'scroll',
//unique hero tag //unique hero tag
path: path,
)), )),
); );
}, },

View File

@ -2,7 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/episodegrid.dart'; import 'package:tsacdop/util/episodegrid.dart';
class MainTab extends StatefulWidget { class MainTab extends StatefulWidget {

View File

@ -0,0 +1,34 @@
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tsacdop/class/podcast_group.dart';
class KeyValueStorage {
final String key;
KeyValueStorage(this.key);
Future<List<GroupEntity>> getGroups() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getString(key) == null) {
PodcastGroup home = PodcastGroup('Home');
await prefs.setString(
key,
json.encode({
'groups': [home.toEntity().toJson()]
}));}
print(prefs.getString(key));
return json
.decode(prefs.getString(key))['groups']
.cast<Map<String, Object>>()
.map<GroupEntity>(GroupEntity.fromJson)
.toList(growable: false);
}
Future<bool> saveGroup(List<GroupEntity> groupList) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString(
key,
json.encode(
{'groups': groupList.map((group) => group.toJson()).toList()}));
}
}

View File

@ -5,8 +5,8 @@ import 'package:path/path.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/webfeed/webfeed.dart'; import 'package:tsacdop/webfeed/webfeed.dart';
class DBHelper { class DBHelper {
@ -25,49 +25,59 @@ class DBHelper {
} }
void _onCreate(Database db, int version) async { void _onCreate(Database db, int version) async {
await db.execute( await db
"""CREATE TABLE PodcastLocal(id INTEGER PRIMARY KEY,title TEXT, .execute("""CREATE TABLE PodcastLocal(id TEXT PRIMARY KEY,title TEXT,
imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT, imageUrl TEXT,rssUrl TEXT UNIQUE,primaryColor TEXT,author TEXT,
description TEXT, add_date INTEGER, order_id INTEGER default 0)"""); description TEXT, add_date INTEGER, order_id INTEGER DEFAULT 0)""");
await db await db
.execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT, .execute("""CREATE TABLE Episodes(id INTEGER PRIMARY KEY,title TEXT,
enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT, enclosure_url TEXT UNIQUE, enclosure_length INTEGER, pubDate TEXT,
description TEXT, feed_title TEXT, feed_link TEXT, milliseconds INTEGER, description TEXT, feed_title TEXT, feed_link TEXT, milliseconds INTEGER,
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0, duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)"""); downloaded TEXT DEFAULT 'ND', download_date INTEGER DEFAULT 0)""");
await db.execute(
"""CREATE TABLE Setting(id INTEGER PRIMARY KEY, setting TEXT, setting_value INTEGER DEFAULT 0)""");
await db
.execute("""INSERT INTO Setting (setting) VALUES('podcasts_order') """);
} }
Future<List<PodcastLocal>> getPodcastLocal() async { Future<List<PodcastLocal>> getPodcastLocal(
List<String> podcasts, int podcastsOrder) async {
var dbClient = await database; var dbClient = await database;
//query podcasts order setting List<PodcastLocal> podcastLocal = List();
List<Map> setting = await dbClient.rawQuery("SELECT setting_value FROM Setting WHERE setting = 'podcasts_order'");
int podcastsOrder = setting.first['setting_value'];
List<Map> list;
if (podcastsOrder == 0)
{ list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal ORDER BY add_date DESC');
print('Get podcasts list Ordered by 0');}
else if (podcastsOrder == 1)
{ list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal ORDER BY add_date');}
else if (podcastsOrder ==2)
{ list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal ORDER BY order_id');
print('Get podcasts list Ordered by 2');}
await Future.forEach(podcasts, (s) async {
List<Map> list;
if (podcastsOrder == 0) {
list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal WHERE id = ? ORDER BY add_date DESC',
[s]);
} else if (podcastsOrder == 1) {
list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal WHERE id = ? ORDER BY add_date',
[s]);
} else if (podcastsOrder == 2) {
list = await dbClient.rawQuery(
'SELECT id, title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal WHERE id = ? ORDER BY order_id , add_date DESC',
[s]);
print('Get podcasts list Ordered by 2');
}
podcastLocal.add(PodcastLocal(
list.first['title'],
list.first['imageUrl'],
list.first['rssUrl'],
list.first['primaryColor'],
list.first['author'],
id: list.first['id']));
});
return podcastLocal;
}
Future<List<PodcastLocal>> getPodcastLocalAll() async {
var dbClient = await database;
List<Map> list = await dbClient.rawQuery(
'SELECT title, imageUrl, rssUrl, primaryColor, author FROM PodcastLocal ORDER BY add_date DESC');
List<PodcastLocal> podcastLocal = List(); List<PodcastLocal> podcastLocal = List();
for (int i = 0; i < list.length; i++) { for (int i = 0; i < list.length; i++) {
podcastLocal.add(PodcastLocal( podcastLocal.add(PodcastLocal(list[i]['title'], list[i]['imageUrl'],
list[i]['title'], list[i]['rssUrl'], list[i]['primaryColor'], list[i]['author'],
list[i]['imageUrl'], id: list[i]['id']));
list[i]['rssUrl'],
list[i]['primaryColor'],
list[i]['author'],
));
} }
return podcastLocal; return podcastLocal;
} }
@ -75,22 +85,12 @@ class DBHelper {
//save podcast order adter user save //save podcast order adter user save
saveOrder(List<PodcastLocal> podcastList) async { saveOrder(List<PodcastLocal> podcastList) async {
var dbClient = await database; var dbClient = await database;
for (int i = 0; i < podcastList.length; i++){ for (int i = 0; i < podcastList.length; i++) {
await dbClient.rawUpdate( await dbClient.rawUpdate(
"UPDATE OR IGNORE PodcastLocal SET order_id = ? WHERE title = ?", "UPDATE OR IGNORE PodcastLocal SET order_id = ? WHERE title = ?",
[i, podcastList[i].title]); [i, podcastList[i].title]);
print(podcastList[i].title); print(podcastList[i].title);
} }
await dbClient.rawUpdate(
"UPDATE OR IGNORE Setting SET setting_value = 2 WHERE setting = 'podcasts_order' ");
print('Changed order');
}
updateOrderSetting(int value) async{
var dbClient = await database;
await dbClient.rawUpdate(
"UPDATE OR IGNORE Setting SET setting_value = ? WHERE setting = 'podcasts_order'",[value]);
} }
Future savePodcastLocal(PodcastLocal podcastLocal) async { Future savePodcastLocal(PodcastLocal podcastLocal) async {
@ -99,9 +99,10 @@ class DBHelper {
var dbClient = await database; var dbClient = await database;
await dbClient.transaction((txn) async { await dbClient.transaction((txn) async {
return await txn.rawInsert( return await txn.rawInsert(
"""INSERT OR IGNORE INTO PodcastLocal (title, imageUrl, rssUrl, """INSERT OR IGNORE INTO PodcastLocal (id, title, imageUrl, rssUrl,
primaryColor, author, description, add_date) VALUES(?, ?, ?, ?, ?, ?, ?)""", primaryColor, author, description, add_date) VALUES(?, ?, ?, ?, ?, ?, ?, ?)""",
[ [
podcastLocal.id,
podcastLocal.title, podcastLocal.title,
podcastLocal.imageUrl, podcastLocal.imageUrl,
podcastLocal.rssUrl, podcastLocal.rssUrl,
@ -188,22 +189,22 @@ class DBHelper {
} else { } else {
for (int i = 0; i < (_result - _count); i++) { for (int i = 0; i < (_result - _count); i++) {
print(_p.items[i].title); print(_p.items[i].title);
_p.items[i].itunes.title != null _title = _p.items[i].itunes.title ?? _p.items[i].title;
? _title = _p.items[i].itunes.title _p.items[i].itunes.summary.contains('<')
: _title = _p.items[i].title;
_p.items[i].itunes.summary != null
? _description = _p.items[i].itunes.summary ? _description = _p.items[i].itunes.summary
: _description = _p.items[i].description; : _description = _p.items[i].description;
isXimalaya(_p.items[i].enclosure.url) isXimalaya(_p.items[i].enclosure.url)
? _url = _p.items[i].enclosure.url.split('=').last ? _url = _p.items[i].enclosure.url.split('=').last
: _url = _p.items[i].enclosure.url; : _url = _p.items[i].enclosure.url;
final _length = _p.items[i].enclosure.length; final _length = _p.items[i].enclosure.length;
final _pubDate = _p.items[i].pubDate; final _pubDate = _p.items[i].pubDate;
final _date = _parsePubDate(_pubDate); final _date = _parsePubDate(_pubDate);
final _milliseconds = _date.millisecondsSinceEpoch; final _milliseconds = _date.millisecondsSinceEpoch;
(_p.items[i].itunes.duration != null)
? _duration = _p.items[i].itunes.duration.inMinutes _duration = _p.items[i].itunes.duration?.inMinutes ?? 0;
: _duration = 0;
final _explicit = getExplicit(_p.items[i].itunes.explicit); final _explicit = getExplicit(_p.items[i].itunes.explicit);
if (_p.items[i].enclosure.url != null) { if (_p.items[i].enclosure.url != null) {
await dbClient.transaction((txn) { await dbClient.transaction((txn) {
@ -384,7 +385,6 @@ class DBHelper {
int count = await dbClient.rawUpdate( int count = await dbClient.rawUpdate(
"UPDATE Episodes SET downloaded = ?, download_date = ? WHERE enclosure_url = ?", "UPDATE Episodes SET downloaded = ?, download_date = ? WHERE enclosure_url = ?",
[id, _milliseconds, url]); [id, _milliseconds, url]);
print('Downloaded ' + url);
return count; return count;
} }

View File

@ -1,8 +1,12 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_statusbarcolor/flutter_statusbarcolor.dart'; import 'package:flutter_statusbarcolor/flutter_statusbarcolor.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/home/appbar/addpodcast.dart'; import 'package:tsacdop/home/appbar/addpodcast.dart';
import 'package:tsacdop/class/audiostate.dart'; import 'package:tsacdop/class/audiostate.dart';
import 'package:tsacdop/class/importompl.dart'; import 'package:tsacdop/class/importompl.dart';
@ -15,6 +19,7 @@ void main() async {
ChangeNotifierProvider(create: (context) => Urlchange()), ChangeNotifierProvider(create: (context) => Urlchange()),
ChangeNotifierProvider(create: (context) => ImportOmpl()), ChangeNotifierProvider(create: (context) => ImportOmpl()),
ChangeNotifierProvider(create: (context) => SettingState()), ChangeNotifierProvider(create: (context) => SettingState()),
ChangeNotifierProvider(create: (context) => GroupList()),
], ],
child: MyApp(), child: MyApp(),
), ),
@ -23,22 +28,13 @@ void main() async {
await FlutterDownloader.initialize(); await FlutterDownloader.initialize();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await FlutterStatusbarcolor.setStatusBarColor(Colors.grey[100]); await FlutterStatusbarcolor.setStatusBarColor(Colors.grey[100]);
if (useWhiteForeground(Colors.grey[100])) {
FlutterStatusbarcolor.setStatusBarWhiteForeground(true);
} else {
FlutterStatusbarcolor.setStatusBarWhiteForeground(false);
}
await FlutterStatusbarcolor.setNavigationBarColor(Colors.grey[100]); await FlutterStatusbarcolor.setNavigationBarColor(Colors.grey[100]);
if (useWhiteForeground(Colors.grey[100])) {
FlutterStatusbarcolor.setNavigationBarWhiteForeground(true);
} else {
FlutterStatusbarcolor.setNavigationBarWhiteForeground(false);
}
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'TsacDop', title: 'TsacDop',

View File

@ -5,7 +5,7 @@ import 'dart:async';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/episodebrief.dart'; import 'package:tsacdop/class/episodebrief.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/util/episodegrid.dart'; import 'package:tsacdop/util/episodegrid.dart';
import 'package:tsacdop/webfeed/webfeed.dart'; import 'package:tsacdop/webfeed/webfeed.dart';

View File

@ -0,0 +1,398 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/class/settingstate.dart';
class PodcastGroupList extends StatefulWidget {
final PodcastGroup group;
PodcastGroupList({this.group, Key key}) : super(key: key);
@override
_PodcastGroupListState createState() => _PodcastGroupListState();
}
class _PodcastGroupListState extends State<PodcastGroupList> {
bool _loading;
bool _loadSave;
String dir;
@override
void initState() {
super.initState();
_loading = false;
_loadSave = false;
getApplicationDocumentsDirectory().then((value) {
dir = value.path;
setState(() {
_loading = true;
});
});
}
Widget _saveButton(BuildContext context) {
var _settingState = Provider.of<SettingState>(context);
_saveOrder(List<PodcastLocal> podcastList) async {
var dbHelper = DBHelper();
await dbHelper.saveOrder(podcastList);
}
var podcastList = widget.group.podcasts;
return Container(
child: InkWell(
child: AnimatedContainer(
duration: Duration(milliseconds: 800),
width: _loadSave ? 70 : 0,
height: 40,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(Radius.circular(5.0))),
alignment: Alignment.center,
child: Text(
'Save',
style: TextStyle(color: Colors.white),
maxLines: 1,
)),
onTap: () async {
await _saveOrder(podcastList);
Fluttertoast.showToast(
msg: 'Setting Saved',
gravity: ToastGravity.BOTTOM,
);
_settingState.subscribeUpdate = Update.justupdate;
setState(() {
_loadSave = false;
});
},
),
);
}
@override
Widget build(BuildContext context) {
return widget.group.podcastList.length == 0
? Center()
: Container(
color: Colors.grey[100],
child: Stack(
children: <Widget>[
ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final PodcastLocal podcast =
widget.group.podcasts.removeAt(oldIndex);
widget.group.podcasts.insert(newIndex, podcast);
_loadSave = true;
});
},
children:
widget.group.podcasts.map<Widget>((PodcastLocal podcastLocal) {
return Container(
decoration: BoxDecoration(color: Colors.grey[100]),
key: ObjectKey(podcastLocal.title),
child: !_loading
? CircularProgressIndicator()
: PodcastCard(
path: dir,
podcastLocal: podcastLocal,
group: widget.group.name,
),
);
}).toList(),
),
AnimatedPositioned(
duration: Duration(seconds: 1),
bottom: 30,
right: _loadSave ? 50 : 0,
child: Center(),
//_saveButton(context),
),
],
),
);
}
}
class PodcastCard extends StatefulWidget {
final PodcastLocal podcastLocal;
final String path;
final String group;
PodcastCard({this.podcastLocal, this.path, this.group, Key key})
: super(key: key);
@override
_PodcastCardState createState() => _PodcastCardState();
}
class _PodcastCardState extends State<PodcastCard> {
bool _loadMenu;
bool _remove;
bool _addGroup;
bool _loadGroup;
List<String> _selectedGroups;
List<String> _belongGroups;
Color _c;
_unSubscribe(String title) async {
var dbHelper = DBHelper();
await dbHelper.delPodcastLocal(title);
}
@override
void initState() {
super.initState();
_loadMenu = false;
_remove = false;
_addGroup = false;
_loadGroup = false;
}
Widget _buttonOnMenu(Widget widget, VoidCallback onTap) => Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Container(
height: 50.0,
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: widget),
),
);
@override
Widget build(BuildContext context) {
var color = json.decode(widget.podcastLocal.primaryColor);
(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);
double _width = MediaQuery.of(context).size.width;
var _settingState = Provider.of<SettingState>(context);
var _groupList = Provider.of<GroupList>(context);
_selectedGroups = _groupList.groups.map((e) => e.name).toList();
_belongGroups = _groupList
.getPodcastGroup(widget.podcastLocal.id)
.map((e) => e.name)
.toList();
return _remove
? Center()
: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
InkWell(
onTap: () => setState(() => _loadMenu = !_loadMenu),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12),
height: 100,
child: Row(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.path}/${widget.podcastLocal.title}.png")),
),
),
),
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),
),
),
!_loadGroup
? Center()
: Row(
children: _belongGroups.map((group) {
return Container(
padding: EdgeInsets.only(right: 5.0),
child: Text(group));
}).toList(),
),
],
)),
Spacer(),
Icon(_loadMenu
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down),
Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
),
OutlineButton(
child: Text('Remove'),
onPressed: () {
showDialog(
context: context,
child: AlertDialog(
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: () {
_unSubscribe(widget.podcastLocal.title);
_settingState.subscribeUpdate =
Update.justupdate;
Navigator.of(context).pop();
},
child: Text(
'CONFIRM',
style: TextStyle(color: Colors.red),
),
)
],
));
},
),
]),
),
),
!_loadMenu
? Center()
: Container(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[100],
border: Border(
bottom: BorderSide(color: Colors.grey[300]),
top: BorderSide(color: Colors.grey[300]))),
height: 50,
child: _addGroup
? Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 4,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Consumer<GroupList>(
builder: (_, groupList, __) => Row(
children: groupList.groups
.map<Widget>(
(PodcastGroup group) {
return Container(
padding: EdgeInsets.only(left: 5.0),
child: FilterChip(
key: ValueKey<String>(group.name),
label: Text(group.name),
selected: _belongGroups
.contains(group.name) &&
_selectedGroups
.contains(group.name),
onSelected: (bool value) {
setState(() {
if (!value) {
_selectedGroups
.remove(group.name);
} else {
_selectedGroups
.add(group.name);
}
});
},
),
);
}).toList()),
),
),
),
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,
);
_settingState.subscribeUpdate =
Update.justupdate;
Fluttertoast.showToast(
msg: 'Setting Saved',
gravity: ToastGravity.BOTTOM,
);
if (!_selectedGroups
.contains(widget.group)) {
print(widget.group);
setState(() {
_remove = true;
});
}
} 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), () {}),
_buttonOnMenu(Icon(Icons.add), () {
setState(() {
_addGroup = true;
});
}),
_buttonOnMenu(
Icon(Icons.notifications), () {})
],
),
),
),
],
);
}
}

View File

@ -6,9 +6,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcastlocal.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/class/settingstate.dart';
import 'package:tsacdop/local_storage/sqflite_localpodcast.dart';
import 'package:tsacdop/podcasts/podcastdetail.dart'; import 'package:tsacdop/podcasts/podcastdetail.dart';
import 'package:tsacdop/util/pageroute.dart'; import 'package:tsacdop/util/pageroute.dart';
@ -48,12 +50,14 @@ class _AboutPodcastState extends State<AboutPodcast> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var _settingState = Provider.of<SettingState>(context);
return AlertDialog( return AlertDialog(
actions: <Widget>[ actions: <Widget>[
FlatButton( FlatButton(
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
onPressed: () { onPressed: () {
_unSubscribe(widget.podcastLocal.title); _unSubscribe(widget.podcastLocal.title);
_settingState.subscribeUpdate = Update.justupdate;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
color: Colors.grey[200], color: Colors.grey[200],
@ -88,95 +92,104 @@ class PodcastList extends StatefulWidget {
class _PodcastListState extends State<PodcastList> { class _PodcastListState extends State<PodcastList> {
var dir; var dir;
Future<List<PodcastLocal>> getPodcastLocal() async { Future<List<PodcastLocal>> getPodcastLocal() async {
dir = await getApplicationDocumentsDirectory(); dir = await getApplicationDocumentsDirectory();
var dbHelper = DBHelper(); var dbHelper = DBHelper();
var podcastList = await dbHelper.getPodcastLocal(); var podcastList = await dbHelper.getPodcastLocalAll();
return podcastList; return podcastList;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double _width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
return Container( return Scaffold(
color: Colors.grey[100], appBar: AppBar(
child: FutureBuilder<List<PodcastLocal>>( title: Text('Podcasts'),
future: getPodcastLocal(), centerTitle: true,
builder: (context, snapshot) { backgroundColor: Colors.grey[100],
if (snapshot.hasData) { elevation: 0,
return CustomScrollView( ),
primary: false, body: Container(
slivers: <Widget>[ color: Colors.grey[100],
SliverPadding( child: FutureBuilder<List<PodcastLocal>>(
padding: const EdgeInsets.all(10.0), future: getPodcastLocal(),
sliver: SliverGrid( builder: (context, snapshot) {
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( if (snapshot.hasData) {
childAspectRatio: 0.8, return CustomScrollView(
crossAxisCount: 3, primary: false,
), slivers: <Widget>[
delegate: SliverChildBuilderDelegate( SliverPadding(
(BuildContext context, int index) { padding: const EdgeInsets.all(10.0),
return InkWell( sliver: SliverGrid(
onTap: () { gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
Navigator.push( childAspectRatio: 0.8,
context, crossAxisCount: 3,
ScaleRoute( ),
page: PodcastDetail( delegate: SliverChildBuilderDelegate(
podcastLocal: snapshot.data[index], (BuildContext context, int index) {
)), return InkWell(
); onTap: () {
}, Navigator.push(
onLongPress: () { context,
showDialog( ScaleRoute(
context: context, page: PodcastDetail(
builder: (BuildContext context) => AboutPodcast( podcastLocal: snapshot.data[index],
podcastLocal: snapshot.data[index]), )),
).then((_) => setState(() {})); );
}, },
child: Container( onLongPress: () {
alignment: Alignment.center, showDialog(
child: Column( context: context,
mainAxisAlignment: MainAxisAlignment.start, builder: (BuildContext context) => AboutPodcast(
children: <Widget>[ podcastLocal: snapshot.data[index]),
Container( ).then((_) => setState(() {}));
height: 10.0, },
), child: Container(
ClipRRect( alignment: Alignment.center,
borderRadius: child: Column(
BorderRadius.all(Radius.circular(_width/8)), mainAxisAlignment: MainAxisAlignment.start,
child: Container( children: <Widget>[
height: _width/4, Container(
width: _width/4, height: 10.0,
child: Image.file(File(
"${dir.path}/${snapshot.data[index].title}.png")),
), ),
), ClipRRect(
Container( borderRadius: BorderRadius.all(
padding: EdgeInsets.all(4.0), Radius.circular(_width / 8)),
child: Text( child: Container(
snapshot.data[index].title, height: _width / 4,
textAlign: TextAlign.center, width: _width / 4,
style: TextStyle( child: Image.file(File(
fontSize: 16.0, "${dir.path}/${snapshot.data[index].title}.png")),
color: Colors.black.withOpacity(0.5),
), ),
maxLines: 2,
), ),
), Container(
], padding: EdgeInsets.all(4.0),
child: Text(
snapshot.data[index].title,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
color: Colors.black.withOpacity(0.5),
),
maxLines: 2,
),
),
],
),
), ),
), );
); },
}, childCount: snapshot.data.length,
childCount: snapshot.data.length, ),
), ),
), ),
), ],
], );
); }
} return Text('NoData');
return Text('NoData'); },
}, ),
), ),
); );
} }

View File

@ -1,13 +1,9 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/class/podcastlocal.dart'; import 'package:tsacdop/class/podcast_group.dart';
import 'package:tsacdop/class/sqflite_localpodcast.dart'; import 'package:tsacdop/podcasts/podcastgroup.dart';
import 'package:tsacdop/class/settingstate.dart'; import 'package:tsacdop/podcasts/podcastlist.dart';
import 'package:tsacdop/util/pageroute.dart';
class PodcastManage extends StatefulWidget { class PodcastManage extends StatefulWidget {
@override @override
@ -15,152 +11,208 @@ class PodcastManage extends StatefulWidget {
} }
class _PodcastManageState extends State<PodcastManage> { class _PodcastManageState extends State<PodcastManage> {
var dir; Decoration getIndicator() {
bool _loading; return const UnderlineTabIndicator(
bool _loadSave; borderSide: BorderSide(color: Colors.red, width: 2),
Color _c; insets: EdgeInsets.only(
double _width; top: 10.0,
List<PodcastLocal> podcastList; ));
getPodcastLocal() async {
dir = await getApplicationDocumentsDirectory();
var dbHelper = DBHelper();
podcastList = await dbHelper.getPodcastLocal();
setState(() {
_loading = true;
});
}
_unSubscribe(String title) async {
var dbHelper = DBHelper();
await dbHelper.delPodcastLocal(title);
print('Unsubscribe');
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loading = false;
_loadSave = false;
getPodcastLocal();
}
void _onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final PodcastLocal podcast = podcastList.removeAt(oldIndex);
podcastList.insert(newIndex, podcast);
_loadSave = true;
});
}
_saveOrder(List<PodcastLocal> podcastList) async {
var dbHelper = DBHelper();
await dbHelper.saveOrder(podcastList);
}
Widget _podcastCard(BuildContext context, PodcastLocal podcastLocal) {
var _settingState = Provider.of<SettingState>(context);
return Container(
padding: EdgeInsets.symmetric(horizontal: 12),
height: 100,
child: Row(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("${dir.path}/${podcastLocal.title}.png")),
),
),
),
Container(
width: _width / 2,
padding: EdgeInsets.symmetric(horizontal: 10),
child: Text(
podcastLocal.title,
maxLines: 2,
overflow: TextOverflow.fade,
)),
Spacer(),
OutlineButton(
child: Text('Unsubscribe'),
onPressed: () {
_unSubscribe(podcastLocal.title);
_settingState.subscribeUpdate = Setting.start;
setState(() {
getPodcastLocal();
});
},
),
]),
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_width = MediaQuery.of(context).size.width;
var _settingState = Provider.of<SettingState>(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Podcasts'),
backgroundColor: Colors.grey[100],
elevation: 0, elevation: 0,
centerTitle: true, centerTitle: true,
backgroundColor: Colors.grey[100],
title: Text('Groups'),
actions: <Widget>[ actions: <Widget>[
!_loadSave OrderMenu(),
? Center()
: InkWell(
child: Container(
padding: EdgeInsets.all(20.0),
alignment: Alignment.center,
child: Text('Save')),
onTap: () async{
await _saveOrder(podcastList);
Fluttertoast.showToast(
msg: 'Saved',
gravity: ToastGravity.BOTTOM,
);
_settingState.subscribeUpdate = Setting.start;
setState(() {
_loadSave = false;
});
},
),
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
], ],
), ),
body: Container( body: Consumer<GroupList>(builder: (_, groupList, __) {
color: Colors.grey[100], bool _isLoading = groupList.isLoading;
child: !_loading List<PodcastGroup> _groups = groupList.groups;
? CircularProgressIndicator() return _isLoading
: ReorderableListView( ? Center()
onReorder: _onReorder, : DefaultTabController(
children: podcastList.map<Widget>((PodcastLocal podcastLocal) { length: _groups.length,
var color = json.decode(podcastLocal.primaryColor); child: Column(
(color[0] > 200 && color[1] > 200 && color[2] > 200) mainAxisAlignment: MainAxisAlignment.start,
? _c = Color.fromRGBO( mainAxisSize: MainAxisSize.min,
(255 - color[0]), 255 - color[1], 255 - color[2], 1.0) children: <Widget>[
: _c = Color.fromRGBO(color[0], color[1], color[2], 1.0); Row(
return Container( children: <Widget>[
decoration: BoxDecoration(color: Colors.grey[100]), Expanded(
margin: EdgeInsets.symmetric(horizontal: 5.0), child: Container(
key: ObjectKey(podcastLocal.title), height: 50,
child: _podcastCard(context, podcastLocal), padding: EdgeInsets.symmetric(horizontal: 10.0),
); alignment: Alignment.centerLeft,
}).toList(), child: TabBar(
labelPadding: EdgeInsets.only(
top: 5.0,
bottom: 6.0,
left: 10.0,
right: 10.0),
indicator: getIndicator(),
isScrollable: true,
tabs: _groups.map<Tab>((group) {
return Tab(
child: Text(group.name),
);
}).toList(),
),
),
),
Container(
child: FlatButton(
onPressed: () => showDialog(
context: context,
builder: (BuildContext context) =>
AddGroup()),
child: Icon(Icons.add)),
),
],
),
Expanded(
child: Container(
child: TabBarView(
children: _groups.map<Widget>((group) {
return Container(
key: ObjectKey(group),
child: PodcastGroupList(group: group));
}).toList(),),
),
)
],
));
}),
);
}
}
class OrderMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PopupMenuButton(
elevation: 3,
tooltip: 'Menu',
itemBuilder: (context) => [
PopupMenuItem(
value: 1,
child: Text('All Podcasts'),
),
PopupMenuItem(
value: 2,
child: Text('New group'),
)
],
onSelected: (value) {
if (value == 1) {
Navigator.push(context, ScaleRoute(page: PodcastList()));
}
if (value == 2) {
showDialog(
context: context, builder: (BuildContext context) => AddGroup());
}
},
);
}
}
class AddGroup extends StatefulWidget {
@override
_AddGroupState createState() => _AddGroupState();
}
class _AddGroupState extends State<AddGroup> {
TextEditingController _controller;
String _newGroup;
int _error;
@override
void initState() {
super.initState();
_error = 0;
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var groupList = Provider.of<GroupList>(context);
List list = groupList.groups.map((e) => e.name).toList();
return AlertDialog(
elevation: 1,
contentPadding: EdgeInsets.symmetric(horizontal: 20),
titlePadding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20),
actionsPadding: EdgeInsets.all(0),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(
'CANCEL',
style: TextStyle(color: Colors.grey[700]),
),
),
FlatButton(
onPressed: () async {
if (list.contains(_newGroup)) {
setState(() => _error = 1);
} else {
groupList.addGroup(PodcastGroup(_newGroup));
Navigator.of(context).pop();
}
},
child: Text('DONE'),
)
],
title: Text('Create new group'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 10),
hintText: 'New Group',
hintStyle: TextStyle(fontSize: 18),
filled: true,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.white, width: 2.0),
), ),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 2.0),
),
),
cursorRadius: Radius.circular(2),
autofocus: true,
maxLines: 1,
controller: _controller,
onChanged: (value) {
_newGroup = value;
},
),
Container(
alignment: Alignment.centerLeft,
child: (_error == 1)
? Text(
'Group existed',
style: TextStyle(color: Colors.red[400]),
)
: Center(),
),
],
), ),
); );
} }

View File

@ -25,7 +25,6 @@ class EpisodeGrid extends StatelessWidget {
this.showNumber, this.showNumber,
this.heroTag}) this.heroTag})
: super(key: key); : super(key: key);
double _width;
Future<String> _getPath() async { Future<String> _getPath() async {
var dir = await getApplicationDocumentsDirectory(); var dir = await getApplicationDocumentsDirectory();
return dir.path; return dir.path;
@ -33,7 +32,7 @@ class EpisodeGrid extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_width = MediaQuery.of(context).size.width; double _width = MediaQuery.of(context).size.width;
return FutureBuilder( return FutureBuilder(
future: _getPath(), future: _getPath(),
builder: (context, snapshot) { builder: (context, snapshot) {
@ -67,7 +66,8 @@ class EpisodeGrid extends StatelessWidget {
ScaleRoute( ScaleRoute(
page: EpisodeDetail( page: EpisodeDetail(
episodeItem: podcast[index], episodeItem: podcast[index],
heroTag: heroTag)), heroTag: heroTag,
path: snapshot.data,)),
); );
}, },
child: Container( child: Container(

View File

@ -46,14 +46,14 @@ class RssItem {
factory RssItem.parse(XmlElement element) { factory RssItem.parse(XmlElement element) {
return RssItem( return RssItem(
title: findElementOrNull(element, "title")?.text, title: findElementOrNull(element, "title")?.text,
description: findElementOrNull(element, "description")?.text, description: findElementOrNull(element, "description")?.text?.trim() ?? 'No shownote provided for this episode',
link: findElementOrNull(element, "link")?.text, link: findElementOrNull(element, "link")?.text?.trim(),
categories: element.findElements("category").map((element) { categories: element.findElements("category").map((element) {
return RssCategory.parse(element); return RssCategory.parse(element);
}).toList(), }).toList(),
guid: findElementOrNull(element, "guid")?.text, //guid: findElementOrNull(element, "guid")?.text,
pubDate: findElementOrNull(element, "pubDate")?.text, pubDate: findElementOrNull(element, "pubDate")?.text?.trim(),
author: findElementOrNull(element, "author")?.text, author: findElementOrNull(element, "author")?.text?.trim(),
// comments: findElementOrNull(element, "comments")?.text, // comments: findElementOrNull(element, "comments")?.text,
// source: RssSource.parse(findElementOrNull(element, "source")), // source: RssSource.parse(findElementOrNull(element, "source")),
// content: RssContent.parse(findElementOrNull(element, "content:encoded")), // content: RssContent.parse(findElementOrNull(element, "content:encoded")),

View File

@ -45,7 +45,7 @@ class RssItunes {
} }
return RssItunes( return RssItunes(
author: findElementOrNull(element, "itunes:author")?.text?.trim(), author: findElementOrNull(element, "itunes:author")?.text?.trim(),
summary: findElementOrNull(element, "itunes:summary")?.text?.trim(), summary: findElementOrNull(element, "itunes:summary")?.text?.trim() ?? '',
explicit: parseBoolLiteral(element, "itunes:explicit"), explicit: parseBoolLiteral(element, "itunes:explicit"),
title: findElementOrNull(element, "itunes:title")?.text?.trim(), title: findElementOrNull(element, "itunes:title")?.text?.trim(),
// subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(), // subtitle: findElementOrNull(element, "itunes:subtitle")?.text?.trim(),

View File

@ -36,13 +36,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
cached_network_image:
dependency: "direct dev"
description:
name: cached_network_image
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -111,13 +104,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.3"
flutter_downloader: flutter_downloader:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -240,13 +226,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.0.4" version: "0.0.4"
network_image_to_byte:
dependency: "direct dev"
description:
name: network_image_to_byte
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -317,6 +296,34 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.0.5" version: "2.0.5"
shared_preferences:
dependency: "direct dev"
description:
name: shared_preferences
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6+1"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1+5"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.2+3"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -414,7 +421,7 @@ packages:
source: hosted source: hosted
version: "0.1.1" version: "0.1.1"
uuid: uuid:
dependency: transitive dependency: "direct dev"
description: description:
name: uuid name: uuid
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
@ -443,4 +450,4 @@ packages:
version: "3.5.0" version: "3.5.0"
sdks: sdks:
dart: ">=2.6.0 <3.0.0" dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.13 <2.0.0" flutter: ">=1.12.13+hotfix.4 <2.0.0"

View File

@ -29,16 +29,14 @@ dev_dependencies:
sdk: flutter sdk: flutter
flutter_statusbarcolor: ^0.2.3 flutter_statusbarcolor: ^0.2.3
json_annotation: any json_annotation: any
cached_network_image: any
sqflite: any sqflite: any
flutter_html: any flutter_html: ^0.11.1
webfeed: any webfeed: any
path_provider: any path_provider: any
color_thief_flutter: ^1.0.1 color_thief_flutter: ^1.0.1
provider: ^4.0.1 provider: ^4.0.1
google_fonts: ^0.3.2 google_fonts: ^0.3.2
dio: ^3.0.8 dio: ^3.0.8
network_image_to_byte: ^0.0.1
file_picker: ^1.2.0 file_picker: ^1.2.0
xml: ^3.5.0 xml: ^3.5.0
marquee: ^1.3.1 marquee: ^1.3.1
@ -49,6 +47,10 @@ dev_dependencies:
intl: ^0.16.1 intl: ^0.16.1
url_launcher: ^5.4.1 url_launcher: ^5.4.1
image: ^2.1.4 image: ^2.1.4
shared_preferences: ^0.5.6+1
uuid: ^2.0.4
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the