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:
parent
d3efce463c
commit
16567a7199
158
lib/class/podcast_group.dart
Normal file
158
lib/class/podcast_group.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,7 +148,8 @@ 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
|
||||||
|
? (widget.episodeItem.description.contains('<'))
|
||||||
? Html(
|
? Html(
|
||||||
data: widget.episodeItem.description,
|
data: widget.episodeItem.description,
|
||||||
onLinkTap: (url) {
|
onLinkTap: (url) {
|
||||||
@ -152,6 +157,9 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||||||
},
|
},
|
||||||
useRichText: true,
|
useRichText: true,
|
||||||
)
|
)
|
||||||
|
: Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Text(widget.episodeItem.description))
|
||||||
: Center(),
|
: Center(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -159,7 +167,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||||||
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")),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -300,8 +310,16 @@ class _MenuBarState extends State<MenuBar> {
|
|||||||
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) {
|
||||||
@ -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)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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;
|
||||||
@ -151,8 +152,8 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||||||
|
|
||||||
_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,13 +233,11 @@ 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>[
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_pauseDownload(task);
|
(task.progress > 0) ? _pauseDownload(task) : null;
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
@ -235,15 +249,12 @@ class _DownloadButtonState extends State<DownloadButton> {
|
|||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
backgroundColor: Colors.grey[500],
|
backgroundColor: Colors.grey[500],
|
||||||
strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.cyan[300]),
|
||||||
value: task.progress / 100,
|
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(
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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(),
|
||||||
|
@ -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,78 +27,160 @@ 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, __) {
|
||||||
|
var groups = groupList.groups;
|
||||||
|
bool isLoading = groupList.isLoading;
|
||||||
|
return isLoading
|
||||||
? Container(
|
? Container(
|
||||||
height: (_width - 20) / 3 + 110,
|
height: (_width - 20) / 3 + 110,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 20.0,
|
||||||
|
width: 20.0,
|
||||||
|
child: CircularProgressIndicator()),
|
||||||
)
|
)
|
||||||
: DefaultTabController(
|
: DefaultTabController(
|
||||||
length: podcastList.length,
|
length: groups[_groupIndex].podcastList.length,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
GestureDetector(
|
||||||
|
onVerticalDragEnd: (event) {
|
||||||
|
if (event.primaryVelocity > 200) {
|
||||||
|
if (groups.length == 1) {
|
||||||
|
Fluttertoast.showToast(
|
||||||
|
msg: 'Add some groups',
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (mounted)
|
||||||
|
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>[
|
children: <Widget>[
|
||||||
Container(
|
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(
|
||||||
|
color: Colors.white10,
|
||||||
height: 70,
|
height: 70,
|
||||||
|
width: _width,
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: TabBar(
|
child: TabBar(
|
||||||
labelPadding: EdgeInsets.only(
|
labelPadding: EdgeInsets.only(
|
||||||
top: 5.0, bottom: 10.0, left: 6.0, right: 6.0),
|
top: 5.0, bottom: 10.0, left: 6.0, right: 6.0),
|
||||||
indicator:
|
indicator: CircleTabIndicator(
|
||||||
CircleTabIndicator(color: Colors.blue, radius: 3),
|
color: Colors.blue, radius: 3),
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
tabs: podcastList.map<Tab>((PodcastLocal podcastLocal) {
|
tabs: groups[_groupIndex].podcasts
|
||||||
|
.map<Tab>((PodcastLocal podcastLocal) {
|
||||||
return Tab(
|
return Tab(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(25.0)),
|
borderRadius:
|
||||||
|
BorderRadius.all(Radius.circular(25.0)),
|
||||||
child: LimitedBox(
|
child: LimitedBox(
|
||||||
maxHeight: 50,
|
maxHeight: 50,
|
||||||
maxWidth: 50,
|
maxWidth: 50,
|
||||||
child: Image.file(
|
child: !_loaded
|
||||||
File("${dir.path}/${podcastLocal.title}.png")),
|
? CircularProgressIndicator()
|
||||||
|
: Image.file(File(
|
||||||
|
"$dir/${podcastLocal.title}.png")),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
Container(
|
Container(
|
||||||
height: (_width - 20) / 3 + 40,
|
height: (_width - 20) / 3 + 40,
|
||||||
margin: EdgeInsets.only(left: 10, right: 10),
|
margin: EdgeInsets.only(left: 10, right: 10),
|
||||||
@ -104,8 +188,8 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
children:
|
children: groups[_groupIndex].podcasts
|
||||||
podcastList.map<Widget>((PodcastLocal podcastLocal) {
|
.map<Widget>((PodcastLocal podcastLocal) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(color: Colors.grey[100]),
|
decoration: BoxDecoration(color: Colors.grey[100]),
|
||||||
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
margin: EdgeInsets.symmetric(horizontal: 5.0),
|
||||||
@ -120,6 +204,7 @@ class _ScrollPodcastsState extends State<ScrollPodcasts> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
34
lib/local_storage/key_value_storage.dart
Normal file
34
lib/local_storage/key_value_storage.dart
Normal 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()}));
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
@ -81,16 +91,6 @@ class DBHelper {
|
|||||||
[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;
|
||||||
}
|
}
|
||||||
|
|
@ -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',
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
398
lib/podcasts/podcastgroup.dart
Normal file
398
lib/podcasts/podcastgroup.dart
Normal 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), () {})
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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,17 +92,25 @@ 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(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Podcasts'),
|
||||||
|
centerTitle: true,
|
||||||
|
backgroundColor: Colors.grey[100],
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
color: Colors.grey[100],
|
color: Colors.grey[100],
|
||||||
child: FutureBuilder<List<PodcastLocal>>(
|
child: FutureBuilder<List<PodcastLocal>>(
|
||||||
future: getPodcastLocal(),
|
future: getPodcastLocal(),
|
||||||
@ -142,8 +154,8 @@ class _PodcastListState extends State<PodcastList> {
|
|||||||
height: 10.0,
|
height: 10.0,
|
||||||
),
|
),
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.all(
|
||||||
BorderRadius.all(Radius.circular(_width/8)),
|
Radius.circular(_width / 8)),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: _width / 4,
|
height: _width / 4,
|
||||||
width: _width / 4,
|
width: _width / 4,
|
||||||
@ -178,6 +190,7 @@ class _PodcastListState extends State<PodcastList> {
|
|||||||
return Text('NoData');
|
return Text('NoData');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,153 +11,209 @@ 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,
|
||||||
|
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(),
|
}).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(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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")),
|
||||||
|
@ -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(),
|
||||||
|
53
pubspec.lock
53
pubspec.lock
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user