parent
62256c7c93
commit
9c13450a9c
|
@ -71,9 +71,12 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -110,7 +113,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
PopupMenuButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 1,
|
||||
elevation: 2,
|
||||
tooltip: 'Menu',
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
|
@ -126,13 +129,13 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
child: CustomPaint(
|
||||
painter: ListenedAllPainter(
|
||||
context.textTheme.bodyText1.color,
|
||||
stroke: 1.5)),
|
||||
stroke: 2)),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5.0),
|
||||
),
|
||||
Text(
|
||||
'Mark listened',
|
||||
'Mark Listened',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -143,7 +146,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
|||
switch (value) {
|
||||
case 0:
|
||||
await _markListened(widget.episodeItem);
|
||||
setState(() {});
|
||||
if (mounted) setState(() {});
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Mark as listened',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
|
@ -413,13 +416,6 @@ class _MenuBarState extends State<MenuBar> {
|
|||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
_markListened(EpisodeBrief episode) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -577,36 +573,6 @@ class _MenuBarState extends State<MenuBar> {
|
|||
),
|
||||
)
|
||||
: snapshot.data.seconds < 0.1
|
||||
// ? Material(
|
||||
// color: Colors.transparent,
|
||||
// child: InkWell(
|
||||
// onTap: () async {
|
||||
// await _markListened(widget.episodeItem);
|
||||
// setState(() {});
|
||||
// Fluttertoast.showToast(
|
||||
// msg: 'Mark as listened',
|
||||
// gravity: ToastGravity.BOTTOM,
|
||||
// );
|
||||
// },
|
||||
// child: Container(
|
||||
// height: 50,
|
||||
// padding: EdgeInsets.only(
|
||||
// left: 15,
|
||||
// right: 15,
|
||||
// top: 12,
|
||||
// bottom: 12),
|
||||
// child: SizedBox(
|
||||
// width: 22,
|
||||
// height: 22,
|
||||
// child: CustomPaint(
|
||||
// painter: MarkListenedPainter(
|
||||
// Colors.grey[700],
|
||||
// stroke: 2.0),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
? SizedBox(
|
||||
width: 1,
|
||||
)
|
||||
|
@ -668,8 +634,7 @@ class _MenuBarState extends State<MenuBar> {
|
|||
),
|
||||
),
|
||||
Selector<AudioPlayerNotifier, Tuple2<EpisodeBrief, bool>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple2(audio.episode, audio.playerRunning),
|
||||
selector: (_, audio) => Tuple2(audio.episode, audio.playerRunning),
|
||||
builder: (_, data, __) {
|
||||
return (widget.episodeItem.title == data.item1?.title &&
|
||||
data.item2)
|
||||
|
|
|
@ -4,6 +4,8 @@ import 'package:tsacdop/util/custompaint.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
import '../util/context_extension.dart';
|
||||
|
||||
const String version = '0.3.3';
|
||||
|
||||
class AboutApp extends StatelessWidget {
|
||||
|
@ -97,13 +99,38 @@ class AboutApp extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 50),
|
||||
child: Text(
|
||||
'Tsacdop is a podcast player developed in flutter, a clean, simply beautiful and friendly app.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
FlatButton(
|
||||
onPressed: () => _launchUrl(
|
||||
'https://tsacdop.stonegate.me/#/privacy'),
|
||||
child: Text('Privacy Policy',
|
||||
style: TextStyle(color: context.accentColor)),
|
||||
),
|
||||
Container(
|
||||
height: 4,
|
||||
width: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: context.accentColor,
|
||||
shape: BoxShape.circle),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () => _launchUrl(
|
||||
'https://tsacdop.stonegate.me/#/changelog'),
|
||||
child: Text('Changelogs',
|
||||
style: TextStyle(color: context.accentColor)),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
),
|
||||
|
|
|
@ -8,6 +8,7 @@ import 'package:tuple/tuple.dart';
|
|||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
|
||||
import '../state/audiostate.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
|
@ -28,6 +29,13 @@ import 'popupmenu.dart';
|
|||
import 'home_groups.dart';
|
||||
import 'download_list.dart';
|
||||
|
||||
const String addFeature = 'addFeature';
|
||||
const String menuFeature = 'menuFeature';
|
||||
const String playlistFeature = 'playlistFeature';
|
||||
const String longTapFeature = 'longTapFeature';
|
||||
const String groupsFeature = 'groupsFeature';
|
||||
const String podcastFeature = 'podcastFeature';
|
||||
|
||||
class Home extends StatefulWidget {
|
||||
@override
|
||||
_HomeState createState() => _HomeState();
|
||||
|
@ -51,11 +59,28 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
}
|
||||
|
||||
var _androidAppRetain = MethodChannel("android_app_retain");
|
||||
var feature1OverflowMode = OverflowMode.clipContent;
|
||||
var feature1EnablePulsingAnimation = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TabController(length: 3, vsync: this);
|
||||
FeatureDiscovery.isDisplayed(context, addFeature).then((value) {
|
||||
if (!value)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FeatureDiscovery.discoverFeatures(
|
||||
context,
|
||||
const <String>{
|
||||
addFeature,
|
||||
menuFeature,
|
||||
playlistFeature,
|
||||
groupsFeature,
|
||||
podcastFeature,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -110,16 +135,67 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
tooltip: 'Add',
|
||||
icon: const Icon(
|
||||
Icons.add_circle_outline),
|
||||
onPressed: () async {
|
||||
await showSearch<int>(
|
||||
context: context,
|
||||
delegate: _delegate,
|
||||
);
|
||||
DescribedFeatureOverlay(
|
||||
featureId: addFeature,
|
||||
tapTarget:
|
||||
Icon(Icons.add_circle_outline),
|
||||
title: const Text(
|
||||
'Tap to search podcast'),
|
||||
backgroundColor: Colors.cyan[600],
|
||||
overflowMode: feature1OverflowMode,
|
||||
onDismiss: () {
|
||||
return Future.value(true);
|
||||
},
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You can search podcast title , key word or RSS link to subscribe new podcast.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery
|
||||
.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: IconButton(
|
||||
tooltip: 'Add',
|
||||
icon: const Icon(
|
||||
Icons.add_circle_outline),
|
||||
onPressed: () async {
|
||||
await showSearch<int>(
|
||||
context: context,
|
||||
delegate: _delegate,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Image(
|
||||
image: Theme.of(context)
|
||||
|
@ -130,7 +206,55 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
'assets/text_light.png'),
|
||||
height: 30,
|
||||
),
|
||||
PopupMenu(),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: menuFeature,
|
||||
tapTarget: Icon(Icons.more_vert),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
onDismiss: () =>
|
||||
Future.value(true),
|
||||
title: const Text(
|
||||
'Tap to import OMPL'),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You can import OMPL file, open setting or refresh all podcast at once here.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color: Colors
|
||||
.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery
|
||||
.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PopupMenu()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -141,10 +265,69 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: ScrollPodcasts(),
|
||||
return DescribedFeatureOverlay(
|
||||
featureId: groupsFeature,
|
||||
tapTarget: Center(
|
||||
child: Text(
|
||||
'Podcast View',
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title: const Text(
|
||||
'Scroll vertically to switch groups'),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You can tap See All to add groups or manage podcasts.'),
|
||||
Row(
|
||||
children: [
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color:
|
||||
Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color:
|
||||
Colors.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
width: width,
|
||||
child: ScrollPodcasts(),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: 1,
|
||||
|
@ -178,7 +361,63 @@ class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
|
|||
controller: _controller,
|
||||
children: <Widget>[
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab0'), _RecentUpdate()),
|
||||
Key('tab0'),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: podcastFeature,
|
||||
tapTarget: Text('Episode View',
|
||||
textAlign: TextAlign.center),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
enablePulsingAnimation: false,
|
||||
onDismiss: () => Future.value(true),
|
||||
title: const Text(
|
||||
'Long tap to play episode instantly'),
|
||||
description: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You can long tap to play episode or add episode to playlist.'),
|
||||
Row(
|
||||
children: [
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color:
|
||||
Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery
|
||||
.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 5)),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding:
|
||||
const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(
|
||||
color:
|
||||
Colors.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery.dismissAll(
|
||||
context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
child: _RecentUpdate())),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
Key('tab1'), _MyFavorite()),
|
||||
NestedScrollViewInnerScrollPositionKeyWidget(
|
||||
|
@ -227,7 +466,41 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
|||
children: <Widget>[
|
||||
_tabBar,
|
||||
Spacer(),
|
||||
PlaylistButton(),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: playlistFeature,
|
||||
tapTarget: Icon(Icons.playlist_play),
|
||||
backgroundColor: Colors.cyan[500],
|
||||
title: const Text('Tap to open playlist'),
|
||||
onDismiss: () => Future.value(true),
|
||||
description: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'You can add episode to playlist by yourself. Episode will be auto removed from playlist when played.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery.completeCurrentStep(context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[600],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () => FeatureDiscovery.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: PlaylistButton()),
|
||||
],
|
||||
),
|
||||
Container(height: 2, color: context.primaryColor),
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
|
||||
import '../type/episodebrief.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
|
|
|
@ -316,6 +316,15 @@ class DBHelper {
|
|||
: PlayHistory(episodeBrief.title, episodeBrief.enclosureUrl, 0, 0);
|
||||
}
|
||||
|
||||
Future<bool> checkMarked(EpisodeBrief episodeBrief) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT title, enclosure_url, seconds, seek_value, add_date FROM PlayHistory
|
||||
WHERE enclosure_url = ? AND seek_value = 1 ORDER BY add_date DESC LIMIT 1""",
|
||||
[episodeBrief.enclosureUrl]);
|
||||
return list.length > 0;
|
||||
}
|
||||
|
||||
DateTime _parsePubDate(String pubDate) {
|
||||
if (pubDate == null) return DateTime.now();
|
||||
DateTime date;
|
||||
|
@ -512,7 +521,6 @@ class DBHelper {
|
|||
url,
|
||||
]);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
int countUpdate = Sqflite.firstIntValue(await dbClient.rawQuery(
|
||||
|
@ -534,7 +542,32 @@ class DBHelper {
|
|||
Future<List<EpisodeBrief>> getRssItem(String id, int i, bool reverse) async {
|
||||
var dbClient = await database;
|
||||
List<EpisodeBrief> episodes = [];
|
||||
if (reverse) {
|
||||
if (i == -1) {
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
|
||||
E.downloaded, P.primaryColor , E.media_id, E.is_new, P.skip_seconds
|
||||
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE P.id = ? ORDER BY E.milliseconds ASC""", [id]);
|
||||
for (int x = 0; x < list.length; x++) {
|
||||
episodes.add(EpisodeBrief(
|
||||
list[x]['title'],
|
||||
list[x]['enclosure_url'],
|
||||
list[x]['enclosure_length'],
|
||||
list[x]['milliseconds'],
|
||||
list[x]['feedTitle'],
|
||||
list[x]['primaryColor'],
|
||||
list[x]['liked'],
|
||||
list[x]['downloaded'],
|
||||
list[x]['duration'],
|
||||
list[x]['explicit'],
|
||||
list[x]['imagePath'],
|
||||
list[x]['media_id'],
|
||||
list[x]['is_new'],
|
||||
list[x]['skip_seconds']));
|
||||
}
|
||||
return episodes;
|
||||
} else if (reverse) {
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
E.milliseconds, P.imagePath, P.title as feedTitle, E.duration, E.explicit, E.liked,
|
||||
|
@ -558,6 +591,7 @@ class DBHelper {
|
|||
list[x]['is_new'],
|
||||
list[x]['skip_seconds']));
|
||||
}
|
||||
return episodes;
|
||||
} else {
|
||||
List<Map> list = await dbClient
|
||||
.rawQuery("""SELECT E.title, E.enclosure_url, E.enclosure_length,
|
||||
|
@ -582,8 +616,8 @@ class DBHelper {
|
|||
list[x]['is_new'],
|
||||
list[x]['skip_seconds']));
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
Future<List<EpisodeBrief>> getNewEpisodes(String id) async {
|
||||
|
@ -1041,13 +1075,12 @@ class DBHelper {
|
|||
}
|
||||
}
|
||||
|
||||
Future<String> getImageUrl(String url) async{
|
||||
Future<String> getImageUrl(String url) async {
|
||||
var dbClient = await database;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""SELECT P.imageUrl FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||
WHERE E.enclosure_url = ?""", [url]);
|
||||
if(list.length ==0)
|
||||
return null;
|
||||
if (list.length == 0) return null;
|
||||
return list.first["imageUrl"];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
|
||||
import 'generated/l10n.dart';
|
||||
import 'state/podcast_group.dart';
|
||||
|
@ -16,6 +18,7 @@ import 'intro_slider/app_intro.dart';
|
|||
|
||||
final SettingState themeSetting = SettingState();
|
||||
Future main() async {
|
||||
timeDilation = 1.0;
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await themeSetting.initData();
|
||||
runApp(
|
||||
|
@ -49,31 +52,34 @@ class MyApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Consumer<SettingState>(
|
||||
builder: (_, setting, __) {
|
||||
return MaterialApp(
|
||||
themeMode: setting.theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Tsacdop',
|
||||
theme: lightTheme.copyWith(
|
||||
accentColor: setting.accentSetColor,
|
||||
cursorColor: setting.accentSetColor,
|
||||
toggleableActiveColor: setting.accentSetColor),
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
accentColor: setting.accentSetColor,
|
||||
primaryColorDark: Colors.grey[800],
|
||||
scaffoldBackgroundColor: setting.realDark ? Colors.black87 : null,
|
||||
primaryColor: setting.realDark ? Colors.black : null,
|
||||
popupMenuTheme: PopupMenuThemeData()
|
||||
.copyWith(color: setting.realDark ? Colors.black87 : null),
|
||||
appBarTheme: AppBarTheme(elevation: 0),
|
||||
cursorColor: setting.accentSetColor),
|
||||
localizationsDelegates: [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: setting.showIntro ? SlideIntro(goto: Goto.home) : Home(),
|
||||
return FeatureDiscovery(
|
||||
child: MaterialApp(
|
||||
themeMode: setting.theme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Tsacdop',
|
||||
theme: lightTheme.copyWith(
|
||||
accentColor: setting.accentSetColor,
|
||||
cursorColor: setting.accentSetColor,
|
||||
toggleableActiveColor: setting.accentSetColor),
|
||||
darkTheme: ThemeData.dark().copyWith(
|
||||
accentColor: setting.accentSetColor,
|
||||
primaryColorDark: Colors.grey[800],
|
||||
scaffoldBackgroundColor:
|
||||
setting.realDark ? Colors.black87 : null,
|
||||
primaryColor: setting.realDark ? Colors.black : null,
|
||||
popupMenuTheme: PopupMenuThemeData()
|
||||
.copyWith(color: setting.realDark ? Colors.black87 : null),
|
||||
appBarTheme: AppBarTheme(elevation: 0),
|
||||
cursorColor: setting.accentSetColor),
|
||||
localizationsDelegates: [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: setting.showIntro ? SlideIntro(goto: Goto.home) : Home(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ import '../type/fireside_data.dart';
|
|||
import '../util/colorize.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/custompaint.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import '../state/audiostate.dart';
|
||||
|
||||
class PodcastDetail extends StatefulWidget {
|
||||
|
@ -95,6 +96,21 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
}
|
||||
}
|
||||
|
||||
_markListened(String podcastId) async {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
List<EpisodeBrief> episodes =
|
||||
await dbHelper.getRssItem(podcastId, -1, true);
|
||||
await Future.forEach(episodes, (episode) async {
|
||||
bool marked = await dbHelper.checkMarked(episode);
|
||||
if (!marked) {
|
||||
final PlayHistory history =
|
||||
PlayHistory(episode.title, episode.enclosureUrl, 0, 1);
|
||||
await dbHelper.saveHistory(history);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget podcastInfo(BuildContext context) {
|
||||
return Container(
|
||||
height: 170,
|
||||
|
@ -183,6 +199,33 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
);
|
||||
}
|
||||
|
||||
_confirmMarkListened(BuildContext context) => generalDialog(
|
||||
context,
|
||||
title: Text('Mark confirm'),
|
||||
content: Text('Confirm mark all episodes listened?'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
await _markListened(widget.podcastLocal.id);
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: context.accentColor),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
double _topHeight = 0;
|
||||
|
||||
ScrollController _controller;
|
||||
|
@ -258,7 +301,9 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
_loadMore = false;
|
||||
});
|
||||
}
|
||||
if (_controller.offset > 0 && mounted && !_scroll )
|
||||
if (_controller.offset > 0 &&
|
||||
mounted &&
|
||||
!_scroll)
|
||||
setState(() {
|
||||
_scroll = true;
|
||||
});
|
||||
|
@ -270,7 +315,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
SliverAppBar(
|
||||
brightness: Brightness.dark,
|
||||
actions: <Widget>[
|
||||
PopupMenuButton<String>(
|
||||
PopupMenuButton<int>(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10))),
|
||||
|
@ -279,8 +324,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
itemBuilder: (context) => [
|
||||
widget.podcastLocal.link != null
|
||||
? PopupMenuItem(
|
||||
value: widget
|
||||
.podcastLocal.link,
|
||||
value: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(
|
||||
left: 10),
|
||||
|
@ -304,7 +348,7 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
)
|
||||
: Center(),
|
||||
PopupMenuItem(
|
||||
value: widget.podcastLocal.rssUrl,
|
||||
value: 1,
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.only(left: 10),
|
||||
|
@ -326,9 +370,54 @@ class _PodcastDetailState extends State<PodcastDetail> {
|
|||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 2,
|
||||
child: Container(
|
||||
padding:
|
||||
EdgeInsets.only(left: 10),
|
||||
child: Row(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 25,
|
||||
height: 25,
|
||||
child: CustomPaint(
|
||||
painter:
|
||||
ListenedAllPainter(
|
||||
context
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.color,
|
||||
stroke: 2)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.symmetric(
|
||||
horizontal: 5.0),
|
||||
),
|
||||
Text(
|
||||
'Mark All Listened',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onSelected: (url) {
|
||||
_launchUrl(url);
|
||||
onSelected: (int value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
_launchUrl(
|
||||
widget.podcastLocal.link);
|
||||
break;
|
||||
case 1:
|
||||
_launchUrl(
|
||||
widget.podcastLocal.rssUrl);
|
||||
break;
|
||||
case 2:
|
||||
_confirmMarkListened(context);
|
||||
break;
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
|
@ -15,6 +15,8 @@ import '../util/pageroute.dart';
|
|||
import '../util/colorize.dart';
|
||||
import '../util/duraiton_picker.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import 'podcastmanage.dart';
|
||||
|
||||
class PodcastGroupList extends StatefulWidget {
|
||||
final PodcastGroup group;
|
||||
|
@ -343,76 +345,41 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
Icons.fast_forward,
|
||||
size: 20 * (_value),
|
||||
), () {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel:
|
||||
MaterialLocalizations.of(context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration:
|
||||
const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context,
|
||||
Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0))),
|
||||
titlePadding: EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: context.width / 3,
|
||||
bottom: 20),
|
||||
title:
|
||||
Text('Skip seconds at the beginning'),
|
||||
content: DurationPicker(
|
||||
duration: Duration(
|
||||
seconds: _skipSeconds ?? 0),
|
||||
onChange: (value) =>
|
||||
_seconds = value.inSeconds,
|
||||
),
|
||||
|
||||
// content: Text('test'),
|
||||
actionsPadding: EdgeInsets.all(10),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_seconds = 0;
|
||||
},
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
saveSkipSeconds(
|
||||
widget.podcastLocal.id,
|
||||
_seconds);
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(
|
||||
color: context.accentColor),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
generalDialog(
|
||||
context,
|
||||
title: Text('Skip seconds at start',
|
||||
maxLines: 2),
|
||||
content: DurationPicker(
|
||||
duration:
|
||||
Duration(seconds: _skipSeconds ?? 0),
|
||||
onChange: (value) =>
|
||||
_seconds = value.inSeconds,
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
_seconds = 0;
|
||||
},
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style:
|
||||
TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
saveSkipSeconds(
|
||||
widget.podcastLocal.id, _seconds);
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(
|
||||
color: context.accentColor),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
_buttonOnMenu(
|
||||
|
@ -421,64 +388,33 @@ class _PodcastCardState extends State<PodcastCard>
|
|||
color: Colors.red,
|
||||
size: 20 * (_value),
|
||||
), () {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel:
|
||||
MaterialLocalizations.of(context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration:
|
||||
const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context,
|
||||
Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
generalDialog(
|
||||
context,
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'Are you sure you want to unsubscribe?'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style:
|
||||
TextStyle(color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0))),
|
||||
titlePadding: EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: context.width / 3,
|
||||
bottom: 20),
|
||||
title: Text('Remove confirm'),
|
||||
content: Text(
|
||||
'Are you sure you want to unsubscribe?'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(
|
||||
color: Colors.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
groupList.removePodcast(
|
||||
widget.podcastLocal.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
groupList.removePodcast(
|
||||
widget.podcastLocal.id);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
@ -534,7 +470,7 @@ class _RenameGroupState extends State<RenameGroup> {
|
|||
elevation: 1,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 20),
|
||||
titlePadding: EdgeInsets.only(
|
||||
top: 20, left: 20, right: context.width / 3, bottom: 20),
|
||||
top: 20, left: 20, right: 20, bottom: 20),
|
||||
actionsPadding: EdgeInsets.all(0),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
|
@ -561,7 +497,7 @@ class _RenameGroupState extends State<RenameGroup> {
|
|||
style: TextStyle(color: Theme.of(context).accentColor)),
|
||||
)
|
||||
],
|
||||
title: Text('Edit group name'),
|
||||
title: SizedBox(width: context.width - 160, child: Text('Edit group name')),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
|
|
|
@ -6,14 +6,20 @@ import 'package:flutter/services.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
|
||||
import '../state/podcast_group.dart';
|
||||
import '../podcasts/podcastgroup.dart';
|
||||
import '../podcasts/podcastlist.dart';
|
||||
import '../util/pageroute.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
import 'custom_tabview.dart';
|
||||
|
||||
const String addGroupFeature = 'addGroupFeature';
|
||||
const String configureGroup = 'configureFeature';
|
||||
const String configurePodcast = 'configurePodcast';
|
||||
|
||||
class PodcastManage extends StatefulWidget {
|
||||
@override
|
||||
_PodcastManageState createState() => _PodcastManageState();
|
||||
|
@ -62,6 +68,16 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
_controller.stop();
|
||||
}
|
||||
});
|
||||
FeatureDiscovery.isDisplayed(context, addGroupFeature).then((value) {
|
||||
if (!value)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
FeatureDiscovery.discoverFeatures(context, const <String>{
|
||||
addGroupFeature,
|
||||
configureGroup,
|
||||
configurePodcast
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -79,54 +95,94 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
} else if (_fraction > 0) {
|
||||
_controller.reverse();
|
||||
}
|
||||
return Transform(
|
||||
alignment: FractionalOffset(0.5, 0.5),
|
||||
transform: Matrix4.rotationY(math.pi * _fraction),
|
||||
child: Container(
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: _fraction > 0.5
|
||||
? Colors.red
|
||||
: Theme.of(context).accentColor,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey[700].withOpacity(0.5),
|
||||
blurRadius: 1,
|
||||
offset: Offset(1, 1),
|
||||
),
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
child: _fraction > 0.5
|
||||
? Icon(LineIcons.save_solid, color: Colors.white)
|
||||
: AnimatedIcon(
|
||||
color: Colors.white,
|
||||
icon: AnimatedIcons.menu_close,
|
||||
progress: _menuController,
|
||||
),
|
||||
// color: Colors.white,
|
||||
return DescribedFeatureOverlay(
|
||||
featureId: configureGroup,
|
||||
tapTarget: Icon(Icons.menu),
|
||||
title: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: const Text('Tap to edit group'),
|
||||
),
|
||||
overflowMode: OverflowMode.clipContent,
|
||||
backgroundColor: Colors.cyan[600],
|
||||
onDismiss: () => Future.value(true),
|
||||
description: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text('You can change group name or delete group here,' +
|
||||
'but home group can not be edited or deleted.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery.completeCurrentStep(context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () => FeatureDiscovery.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Transform(
|
||||
alignment: FractionalOffset(0.5, 0.5),
|
||||
transform: Matrix4.rotationY(math.pi * _fraction),
|
||||
child: Container(
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: _fraction > 0.5
|
||||
? Colors.red
|
||||
: Theme.of(context).accentColor,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey[700].withOpacity(0.5),
|
||||
blurRadius: 1,
|
||||
offset: Offset(1, 1),
|
||||
),
|
||||
]),
|
||||
alignment: Alignment.center,
|
||||
child: _fraction > 0.5
|
||||
? Icon(LineIcons.save_solid, color: Colors.white)
|
||||
: AnimatedIcon(
|
||||
color: Colors.white,
|
||||
icon: AnimatedIcons.menu_close,
|
||||
progress: _menuController,
|
||||
),
|
||||
// color: Colors.white,
|
||||
),
|
||||
onTap: () async {
|
||||
if (_fraction == 0) {
|
||||
!_showSetting
|
||||
? _menuController.forward()
|
||||
: await _menuController.reverse();
|
||||
setState(() {
|
||||
_showSetting = !_showSetting;
|
||||
});
|
||||
} else {
|
||||
groupList.saveOrder(groupList.groups[_index]);
|
||||
groupList
|
||||
.drlFromOrderChanged(groupList.groups[_index].name);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Setting Saved',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
_controller.reverse();
|
||||
}
|
||||
},
|
||||
),
|
||||
onTap: () async {
|
||||
if (_fraction == 0) {
|
||||
!_showSetting
|
||||
? _menuController.forward()
|
||||
: await _menuController.reverse();
|
||||
setState(() {
|
||||
_showSetting = !_showSetting;
|
||||
});
|
||||
} else {
|
||||
groupList.saveOrder(groupList.groups[_index]);
|
||||
groupList.drlFromOrderChanged(groupList.groups[_index].name);
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Setting Saved',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
_controller.reverse();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -148,18 +204,55 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
centerTitle: true,
|
||||
title: Text('Groups'),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () => showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AddGroup()),
|
||||
icon: Icon(Icons.add)),
|
||||
DescribedFeatureOverlay(
|
||||
featureId: addGroupFeature,
|
||||
tapTarget: Icon(Icons.add),
|
||||
title: const Text('Tap to add group'),
|
||||
overflowMode: OverflowMode.clipContent,
|
||||
backgroundColor: Colors.cyan[600],
|
||||
onDismiss: () => Future.value(true),
|
||||
description: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'Default group is home for new podcast, you can create new group and move ' +
|
||||
'podcast to new group, podcast can be added to muilti-groups.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery.completeCurrentStep(context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () => FeatureDiscovery.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: IconButton(
|
||||
onPressed: () => showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AddGroup()),
|
||||
icon: Icon(Icons.add)),
|
||||
),
|
||||
OrderMenu(),
|
||||
],
|
||||
),
|
||||
|
@ -197,9 +290,49 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
_groups[index].name,
|
||||
)),
|
||||
),
|
||||
pageBuilder: (context, index) => Container(
|
||||
key: ValueKey(_groups[index].name),
|
||||
child: PodcastGroupList(group: _groups[index])),
|
||||
pageBuilder: (context, index) =>
|
||||
DescribedFeatureOverlay(
|
||||
featureId: configurePodcast,
|
||||
tapTarget: Text('Podcast'),
|
||||
title: const Text('Long tap to reorder podcast'),
|
||||
overflowMode: OverflowMode.clipContent,
|
||||
onDismiss: () => Future.value(true),
|
||||
enablePulsingAnimation: false,
|
||||
backgroundColor: Colors.cyan[600],
|
||||
description: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
const Text('You can tap to see more options,' +
|
||||
' or long tap to reorder podcast in group.'),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Understood',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () async =>
|
||||
FeatureDiscovery.completeCurrentStep(
|
||||
context),
|
||||
),
|
||||
FlatButton(
|
||||
color: Colors.cyan[500],
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: Text('Dismiss',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.button
|
||||
.copyWith(color: Colors.white)),
|
||||
onPressed: () =>
|
||||
FeatureDiscovery.dismissAll(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Container(
|
||||
key: ValueKey(_groups[index].name),
|
||||
child: PodcastGroupList(group: _groups[index])),
|
||||
),
|
||||
onPositionChange: (value) =>
|
||||
setState(() => _index = value),
|
||||
onScroll: (value) => setState(() => _scroll = value),
|
||||
|
@ -310,116 +443,52 @@ class _PodcastManageState extends State<PodcastManage>
|
|||
'Home group is not supported',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
)
|
||||
: showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel:
|
||||
MaterialLocalizations.of(
|
||||
context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration:
|
||||
const Duration(
|
||||
milliseconds: 300),
|
||||
pageBuilder: (BuildContext
|
||||
context,
|
||||
Animation animaiton,
|
||||
Animation
|
||||
secondaryAnimation) =>
|
||||
AnnotatedRegion<
|
||||
SystemUiOverlayStyle>(
|
||||
value:
|
||||
SystemUiOverlayStyle(
|
||||
statusBarIconBrightness:
|
||||
Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness
|
||||
.light
|
||||
? Color
|
||||
.fromRGBO(
|
||||
113,
|
||||
113,
|
||||
113,
|
||||
1)
|
||||
: Color
|
||||
.fromRGBO(
|
||||
15,
|
||||
15,
|
||||
15,
|
||||
1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius
|
||||
.all(Radius
|
||||
.circular(
|
||||
10.0))),
|
||||
titlePadding:
|
||||
EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 20,
|
||||
right: context
|
||||
.width /
|
||||
3,
|
||||
bottom: 20),
|
||||
title: Text(
|
||||
'Delete confirm'),
|
||||
content: Text(
|
||||
'Are you sure you want to delete this group?' +
|
||||
'Podcasts will be moved to Home group.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop(),
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(
|
||||
color: Colors
|
||||
.grey[
|
||||
600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
if (_index ==
|
||||
groupList
|
||||
.groups
|
||||
.length -
|
||||
1) {
|
||||
setState(() {
|
||||
_index =
|
||||
_index -
|
||||
1;
|
||||
_scroll = 0;
|
||||
});
|
||||
groupList.delGroup(
|
||||
_groups[
|
||||
_index +
|
||||
1]);
|
||||
} else {
|
||||
groupList.delGroup(
|
||||
_groups[
|
||||
_index]);
|
||||
}
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(
|
||||
color: Colors
|
||||
.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
: generalDialog(
|
||||
context,
|
||||
title: Text('Delete confirm'),
|
||||
content: Text(
|
||||
'Are you sure you want to delete this group?' +
|
||||
'Podcasts will be moved to Home group.'),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context)
|
||||
.pop(),
|
||||
child: Text(
|
||||
'CANCEL',
|
||||
style: TextStyle(
|
||||
color: Colors
|
||||
.grey[600]),
|
||||
),
|
||||
),
|
||||
FlatButton(
|
||||
onPressed: () {
|
||||
if (_index ==
|
||||
groupList.groups
|
||||
.length -
|
||||
1) {
|
||||
setState(() {
|
||||
_index = _index - 1;
|
||||
_scroll = 0;
|
||||
});
|
||||
groupList.delGroup(
|
||||
_groups[
|
||||
_index + 1]);
|
||||
} else {
|
||||
groupList.delGroup(
|
||||
_groups[_index]);
|
||||
}
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: Text(
|
||||
'CONFIRM',
|
||||
style: TextStyle(
|
||||
color: Colors.red),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
height: 30,
|
||||
|
@ -533,8 +602,7 @@ class _AddGroupState extends State<AddGroup> {
|
|||
borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
elevation: 1,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 20),
|
||||
titlePadding: EdgeInsets.only(
|
||||
top: 20, left: 20, right: context.width / 3, bottom: 20),
|
||||
titlePadding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 20),
|
||||
actionsPadding: EdgeInsets.all(0),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
|
@ -557,7 +625,8 @@ class _AddGroupState extends State<AddGroup> {
|
|||
style: TextStyle(color: Theme.of(context).accentColor)),
|
||||
)
|
||||
],
|
||||
title: Text('Create new group'),
|
||||
title: SizedBox(
|
||||
width: context.width - 160, child: Text('Create new group')),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
|
|
|
@ -8,12 +8,16 @@ import 'package:line_icons/line_icons.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:feature_discovery/feature_discovery.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import '../util/ompl_build.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../intro_slider/app_intro.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../home/home.dart';
|
||||
import '../podcasts/podcastmanage.dart';
|
||||
import 'theme.dart';
|
||||
import 'layouts.dart';
|
||||
import 'storage.dart';
|
||||
|
@ -256,16 +260,6 @@ class _SettingsState extends State<Settings>
|
|||
shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
onTap: () => _launchUrl(
|
||||
'https://github.com/stonega/tsacdop/releases'),
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 25.0),
|
||||
leading: Icon(LineIcons.map_signs_solid),
|
||||
title: Text('Changelog'),
|
||||
subtitle: Text('List of changes'),
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
|
@ -331,6 +325,31 @@ class _SettingsState extends State<Settings>
|
|||
Divider(
|
||||
height: 2,
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
FeatureDiscovery.clearPreferences(
|
||||
context, const <String>{
|
||||
addFeature,
|
||||
menuFeature,
|
||||
playlistFeature,
|
||||
groupsFeature,
|
||||
addGroupFeature,
|
||||
configureGroup,
|
||||
configurePodcast,
|
||||
podcastFeature
|
||||
});
|
||||
Fluttertoast.showToast(
|
||||
msg:
|
||||
'Discovery Feature Reopened, pleast restart the app',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
},
|
||||
contentPadding:
|
||||
EdgeInsets.symmetric(horizontal: 25.0),
|
||||
leading: Icon(LineIcons.capsules_solid),
|
||||
title: Text('Discovery Features Again'),
|
||||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../state/settingstate.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/general_dialog.dart';
|
||||
|
||||
class ThemeSetting extends StatelessWidget {
|
||||
@override
|
||||
|
@ -134,47 +135,20 @@ class ThemeSetting extends StatelessWidget {
|
|||
),
|
||||
Divider(height: 2),
|
||||
ListTile(
|
||||
onTap: () => showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context)
|
||||
.modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
titlePadding: EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 40,
|
||||
right: context.width / 3,
|
||||
bottom: 0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10.0))),
|
||||
title: Text.rich(
|
||||
TextSpan(text: 'Choose a ', children: [
|
||||
TextSpan(
|
||||
text: 'color',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.accentColor))
|
||||
])),
|
||||
content: ColorPicker(
|
||||
onColorChanged: (value) =>
|
||||
settings.setAccentColor = value,
|
||||
),
|
||||
))),
|
||||
onTap: () => generalDialog(
|
||||
context,
|
||||
title: Text.rich(TextSpan(text: 'Choose a ', children: [
|
||||
TextSpan(
|
||||
text: 'color',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: context.accentColor))
|
||||
])),
|
||||
content: ColorPicker(
|
||||
onColorChanged: (value) =>
|
||||
settings.setAccentColor = value,
|
||||
),
|
||||
),
|
||||
contentPadding: EdgeInsets.only(left: 80.0, right: 25),
|
||||
title: Text('Accent color'),
|
||||
subtitle: Text('Include the overlay color'),
|
||||
|
|
|
@ -137,7 +137,10 @@ class DownloadState extends ChangeNotifier {
|
|||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
String fileName = episode.title +
|
||||
String title = episode.title.trim().substring(0, 1) == '#'
|
||||
? episode.title.trim().substring(1)
|
||||
: episode.title.trim();
|
||||
String fileName = title +
|
||||
datePlus +
|
||||
'.' +
|
||||
episode.enclosureUrl.split('/').last.split('.').last;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'context_extension.dart';
|
||||
|
||||
generalDialog(BuildContext context,
|
||||
{Widget title, Widget content, List<Widget> actions}) =>
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: Colors.black54,
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
pageBuilder: (BuildContext context, Animation animaiton,
|
||||
Animation secondaryAnimation) =>
|
||||
AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor:
|
||||
Theme.of(context).brightness == Brightness.light
|
||||
? Color.fromRGBO(113, 113, 113, 1)
|
||||
: Color.fromRGBO(15, 15, 15, 1),
|
||||
),
|
||||
child: AlertDialog(
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(10.0))),
|
||||
titlePadding: EdgeInsets.all(20),
|
||||
title: SizedBox(width: context.width-160, child: title),
|
||||
content: content,
|
||||
actionsPadding: EdgeInsets.all(10),
|
||||
actions: actions),
|
||||
),
|
||||
);
|
|
@ -44,6 +44,7 @@ dependencies:
|
|||
wc_flutter_share: ^0.2.1
|
||||
video_player: ^0.10.11
|
||||
auto_animated: ^2.1.0
|
||||
feature_discovery: ^0.10.0
|
||||
just_audio:
|
||||
git:
|
||||
url: https://github.com/stonega/just_audio.git
|
||||
|
|
Loading…
Reference in New Issue