Suport chapers in player panel.
This commit is contained in:
parent
9ae9a27206
commit
db5d038e62
|
@ -271,7 +271,7 @@ class _EpisodeDetailState extends State<EpisodeDetail> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_ShowNote(episode: widget.episodeItem),
|
ShowNote(episode: widget.episodeItem),
|
||||||
Selector<AudioPlayerNotifier,
|
Selector<AudioPlayerNotifier,
|
||||||
Tuple2<bool, PlayerHeight>>(
|
Tuple2<bool, PlayerHeight>>(
|
||||||
selector: (_, audio) => Tuple2(
|
selector: (_, audio) => Tuple2(
|
||||||
|
@ -580,9 +580,9 @@ class __MenuBarState extends State<_MenuBar> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ShowNote extends StatelessWidget {
|
class ShowNote extends StatelessWidget {
|
||||||
final EpisodeBrief episode;
|
final EpisodeBrief episode;
|
||||||
const _ShowNote({this.episode, Key key}) : super(key: key);
|
const ShowNote({this.episode, Key key}) : super(key: key);
|
||||||
|
|
||||||
int _getTimeStamp(String url) {
|
int _getTimeStamp(String url) {
|
||||||
final time = url.substring(3).trim();
|
final time = url.substring(3).trim();
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:line_icons/line_icons.dart';
|
import 'package:line_icons/line_icons.dart';
|
||||||
import 'package:marquee/marquee.dart';
|
import 'package:marquee/marquee.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
|
||||||
import '../episodes/episode_detail.dart';
|
import '../episodes/episode_detail.dart';
|
||||||
import '../local_storage/key_value_storage.dart';
|
import '../local_storage/key_value_storage.dart';
|
||||||
import '../local_storage/sqflite_localpodcast.dart';
|
import '../local_storage/sqflite_localpodcast.dart';
|
||||||
import '../playlists/playlist_home.dart';
|
import '../playlists/playlist_home.dart';
|
||||||
import '../state/audio_state.dart';
|
import '../state/audio_state.dart';
|
||||||
|
import '../type/chapter.dart';
|
||||||
import '../type/episodebrief.dart';
|
import '../type/episodebrief.dart';
|
||||||
import '../type/play_histroy.dart';
|
import '../type/play_histroy.dart';
|
||||||
import '../type/playlist.dart';
|
import '../type/playlist.dart';
|
||||||
|
@ -425,6 +431,7 @@ class _PlaylistWidgetState extends State<PlaylistWidget> {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
itemCount: episodes.length,
|
itemCount: episodes.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final isPlaying = episodes[index] != null &&
|
final isPlaying = episodes[index] != null &&
|
||||||
|
@ -886,6 +893,280 @@ class SleepModeState extends State<SleepMode>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ChaptersWidget extends StatefulWidget {
|
||||||
|
ChaptersWidget({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ChaptersWidgetState createState() => _ChaptersWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChaptersWidgetState extends State<ChaptersWidget> {
|
||||||
|
bool _showChapter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_showChapter = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Chapters>> _getChapters(EpisodeBrief episode) async {
|
||||||
|
if (episode.chapterLink == '' || episode.chapterLink == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final file =
|
||||||
|
await DefaultCacheManager().getSingleFile(episode.chapterLink);
|
||||||
|
final response = file.readAsStringSync();
|
||||||
|
var chapterInfo = ChapterInfo.fromJson(jsonDecode(response));
|
||||||
|
return chapterInfo.chapters;
|
||||||
|
} catch (e) {
|
||||||
|
developer.log('Download cahpter error', error: e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _chapterDetailWidget(Chapters chapters) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 60,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: ButtonTheme(
|
||||||
|
height: 28,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
child: OutlineButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(100.0),
|
||||||
|
side: BorderSide(color: context.accentColor)),
|
||||||
|
highlightedBorderColor: Colors.green[700],
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<AudioPlayerNotifier>()
|
||||||
|
.seekTo(chapters.startTime * 1000);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CustomPaint(
|
||||||
|
painter:
|
||||||
|
ListenedPainter(context.textColor, stroke: 2.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Text(
|
||||||
|
chapters.startTime.toTime,
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(chapters.title,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: context.textTheme.bodyText1)),
|
||||||
|
if (chapters.url != '')
|
||||||
|
TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStateProperty.all<Color>(context.accentColor),
|
||||||
|
overlayColor: MaterialStateProperty.all<Color>(
|
||||||
|
context.primaryColor),
|
||||||
|
),
|
||||||
|
onPressed: () => chapters.url.launchUrl,
|
||||||
|
child: Text('Link')),
|
||||||
|
SizedBox(width: 8)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (chapters.img != '') _ChapterImage(chapters.img)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.accentColor.withAlpha(70),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Selector<AudioPlayerNotifier, EpisodeBrief>(
|
||||||
|
selector: (_, audio) => audio.episode,
|
||||||
|
builder: (_, episode, __) => Scrollbar(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _showChapter
|
||||||
|
? FutureBuilder<List<Chapters>>(
|
||||||
|
future: _getChapters(episode),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData) {
|
||||||
|
final data = snapshot.data;
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: data.length,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _chapterDetailWidget(data[index]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Center();
|
||||||
|
})
|
||||||
|
: ListView(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
children: <Widget>[
|
||||||
|
if (episode.episodeImage != '')
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
width: 100,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
imageUrl: episode.episodeImage,
|
||||||
|
placeholderFadeInDuration: Duration.zero,
|
||||||
|
progressIndicatorBuilder: (context, url,
|
||||||
|
downloadProgress) =>
|
||||||
|
Container(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 2,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value:
|
||||||
|
downloadProgress.progress),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) =>
|
||||||
|
Center()),
|
||||||
|
),
|
||||||
|
ShowNote(episode: episode)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 60.0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
context.s.settingsInfo,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
style: TextStyle(
|
||||||
|
color: context.accentColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
Material(
|
||||||
|
borderRadius: BorderRadius.circular(100),
|
||||||
|
color: context.primaryColor,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(15.0),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_showChapter = !_showChapter;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 30.0,
|
||||||
|
width: 30.0,
|
||||||
|
child: !_showChapter
|
||||||
|
? Icon(Icons.bookmark_border_outlined,
|
||||||
|
size: 18)
|
||||||
|
: Icon(Icons.chrome_reader_mode_outlined,
|
||||||
|
size: 18)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChapterImage extends StatefulWidget {
|
||||||
|
final String url;
|
||||||
|
_ChapterImage(this.url, {Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
__ChapterImageState createState() => __ChapterImageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class __ChapterImageState extends State<_ChapterImage> {
|
||||||
|
bool _openFullImage;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_openFullImage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => setState(() => _openFullImage = !_openFullImage),
|
||||||
|
child: ClipRRect(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: [
|
||||||
|
CachedNetworkImage(
|
||||||
|
width: double.infinity,
|
||||||
|
height: _openFullImage ? null : 50,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
imageUrl: widget.url,
|
||||||
|
placeholderFadeInDuration: Duration.zero,
|
||||||
|
progressIndicatorBuilder: (contlext, url, downloadProgress) =>
|
||||||
|
Container(
|
||||||
|
height: 50,
|
||||||
|
width: double.infinity,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 2,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: downloadProgress.progress),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
errorWidget: (context, url, error) => Center()),
|
||||||
|
if (!_openFullImage)
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black38,
|
||||||
|
offset: Offset(0, -5),
|
||||||
|
blurRadius: 20,
|
||||||
|
spreadRadius: 10)
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ControlPanel extends StatefulWidget {
|
class ControlPanel extends StatefulWidget {
|
||||||
ControlPanel(
|
ControlPanel(
|
||||||
{this.onExpand,
|
{this.onExpand,
|
||||||
|
@ -938,7 +1219,7 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_setSpeed = 0;
|
_setSpeed = 0;
|
||||||
_tabController = TabController(vsync: this, length: 2)
|
_tabController = TabController(vsync: this, length: 3)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
setState(() => _tabIndex = _tabController.index);
|
setState(() => _tabIndex = _tabController.index);
|
||||||
});
|
});
|
||||||
|
@ -1260,15 +1541,18 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 20.0),
|
horizontal: 20.0),
|
||||||
child: PlaylistWidget(),
|
child: PlaylistWidget()),
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 20.0),
|
horizontal: 20.0),
|
||||||
child: SleepMode(),
|
child: SleepMode(),
|
||||||
)
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20.0),
|
||||||
|
child: ChaptersWidget()),
|
||||||
]),
|
]),
|
||||||
))),
|
))),
|
||||||
),
|
),
|
||||||
|
@ -1438,14 +1722,14 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: 100,
|
width: 115,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
size: Size(100, 5),
|
size: Size(120, 5),
|
||||||
painter: TabIndicator(
|
painter: TabIndicator(
|
||||||
index: _tabIndex,
|
index: _tabIndex,
|
||||||
indicatorSize: 20,
|
indicatorSize: 10,
|
||||||
fraction:
|
fraction:
|
||||||
(height + 16 - widget.maxHeight) /
|
(height + 16 - widget.maxHeight) /
|
||||||
(context.height -
|
(context.height -
|
||||||
|
@ -1476,17 +1760,21 @@ class _ControlPanelState extends State<ControlPanel>
|
||||||
unselectedLabelColor: context.textColor,
|
unselectedLabelColor: context.textColor,
|
||||||
indicator: BoxDecoration(),
|
indicator: BoxDecoration(),
|
||||||
tabs: [
|
tabs: [
|
||||||
Container(
|
SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
child: Icon(Icons.playlist_play)),
|
child: Icon(Icons.playlist_play)),
|
||||||
Container(
|
SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
child: Transform.rotate(
|
child: Transform.rotate(
|
||||||
angle: math.pi * 0.7,
|
angle: math.pi * 0.7,
|
||||||
child:
|
child:
|
||||||
Icon(Icons.brightness_2, size: 18))),
|
Icon(Icons.brightness_2, size: 18))),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 20,
|
||||||
|
child: Icon(Icons.library_books, size: 18)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -28,7 +28,7 @@ class DBHelper {
|
||||||
var documentsDirectory = await getDatabasesPath();
|
var documentsDirectory = await getDatabasesPath();
|
||||||
var path = join(documentsDirectory, "podcasts.db");
|
var path = join(documentsDirectory, "podcasts.db");
|
||||||
var theDb = await openDatabase(path,
|
var theDb = await openDatabase(path,
|
||||||
version: 5, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
version: 6, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||||
return theDb;
|
return theDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,54 +47,85 @@ class DBHelper {
|
||||||
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
description TEXT, feed_id 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,
|
||||||
liked_date INTEGER DEFAULT 0, downloaded TEXT DEFAULT 'ND',
|
liked_date INTEGER DEFAULT 0, downloaded TEXT DEFAULT 'ND',
|
||||||
download_date INTEGER DEFAULT 0, media_id TEXT, is_new INTEGER DEFAULT 0)""");
|
download_date INTEGER DEFAULT 0, media_id TEXT, is_new INTEGER DEFAULT 0,
|
||||||
|
chapter_link TEXT DEFAULT '', hosts TEXT DEFAULT '', episode_image TEXT DEFAULT '')""");
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT,
|
"""CREATE TABLE PlayHistory(id INTEGER PRIMARY KEY, title TEXT, enclosure_url TEXT,
|
||||||
seconds REAL, seek_value REAL, add_date INTEGER, listen_time INTEGER DEFAULT 0)""");
|
seconds REAL, seek_value REAL, add_date INTEGER, listen_time INTEGER DEFAULT 0)""");
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""CREATE TABLE SubscribeHistory(id TEXT PRIMARY KEY, title TEXT, rss_url TEXT UNIQUE,
|
"""CREATE TABLE SubscribeHistory(id TEXT PRIMARY KEY, title TEXT, rss_url TEXT UNIQUE,
|
||||||
add_date INTEGER, remove_date INTEGER DEFAULT 0, status INTEGER DEFAULT 0)""");
|
add_date INTEGER, remove_date INTEGER DEFAULT 0, status INTEGER DEFAULT 0)""");
|
||||||
|
await db
|
||||||
|
.execute("""CREATE INDEX podcast_search ON PodcastLocal (id, rssUrl);
|
||||||
|
""");
|
||||||
|
await db.execute(
|
||||||
|
"""CREATE INDEX episode_search ON Episodes (enclosure_url, feed_id);
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
void _onUpgrade(Database db, int oldVersion, int newVersion) async {
|
||||||
switch (oldVersion) {
|
switch (oldVersion) {
|
||||||
case (1):
|
case (1):
|
||||||
await db.execute(
|
await _v2Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0 ");
|
await _v3Update(db);
|
||||||
await db.execute(
|
await _v4Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
|
await _v5Update(db);
|
||||||
await db.execute(
|
await _v6Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
|
|
||||||
await db.execute(
|
|
||||||
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
|
|
||||||
await db
|
|
||||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
|
||||||
break;
|
break;
|
||||||
case (2):
|
case (2):
|
||||||
await db.execute(
|
await _v3Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
|
await _v4Update(db);
|
||||||
await db.execute(
|
await _v5Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
|
await _v6Update(db);
|
||||||
await db.execute(
|
|
||||||
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
|
|
||||||
await db
|
|
||||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
|
||||||
break;
|
break;
|
||||||
case (3):
|
case (3):
|
||||||
await db.execute(
|
await _v4Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
|
await _v5Update(db);
|
||||||
await db.execute(
|
await _v6Update(db);
|
||||||
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
|
|
||||||
await db
|
|
||||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
|
||||||
break;
|
break;
|
||||||
case (4):
|
case (4):
|
||||||
await db
|
await _v5Update(db);
|
||||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
await _v6Update(db);
|
||||||
|
break;
|
||||||
|
case (5):
|
||||||
|
await _v6Update(db);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _v2Update(Database db) async {
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _v3Update(Database db) async {
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _v4Update(Database db) async {
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE PodcastLocal ADD skip_seconds_end INTEGER DEFAULT 0 ");
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE PodcastLocal ADD never_update INTEGER DEFAULT 0 ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _v5Update(Database db) async {
|
||||||
|
await db.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _v6Update(Database db) async {
|
||||||
|
await db.execute("ALTER TABLE Episodes ADD chapter_link TEXT DEFAULT '' ");
|
||||||
|
await db.execute("ALTER TABLE Episodes ADD hosts TEXT DEFAULT '' ");
|
||||||
|
await db.execute("ALTER TABLE Episodes ADD episode_image TEXT DEFAULT '' ");
|
||||||
|
await db
|
||||||
|
.execute("""CREATE INDEX podcast_search ON PodcastLocal (id, rssUrl)
|
||||||
|
""");
|
||||||
|
await db.execute(
|
||||||
|
"""CREATE INDEX episode_search ON Episodes (enclosure_url, feed_id)
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts,
|
Future<List<PodcastLocal>> getPodcastLocal(List<String> podcasts,
|
||||||
{bool updateOnly = false}) async {
|
{bool updateOnly = false}) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
|
@ -318,11 +349,10 @@ class DBHelper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updatePodcastImage({String id ,String filePath}) async{
|
Future<void> updatePodcastImage({String id, String filePath}) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
return await dbClient.rawUpdate(
|
return await dbClient.rawUpdate(
|
||||||
"UPDATE PodcastLocal SET imagePath= ? WHERE id = ?",
|
"UPDATE PodcastLocal SET imagePath= ? WHERE id = ?", [filePath, id]);
|
||||||
[filePath, id]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> saveFiresideData(List<String> list) async {
|
Future<int> saveFiresideData(List<String> list) async {
|
||||||
|
@ -616,12 +646,14 @@ class DBHelper {
|
||||||
final milliseconds = date.millisecondsSinceEpoch;
|
final milliseconds = date.millisecondsSinceEpoch;
|
||||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||||
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
final explicit = _getExplicit(feed.items[i].itunes.explicit);
|
||||||
|
final chapter = feed.items[i].podcastChapters?.url ?? '';
|
||||||
|
final image = feed.items[i].itunes.image.href ?? '';
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
await dbClient.transaction((txn) {
|
await dbClient.transaction((txn) {
|
||||||
return txn.rawInsert(
|
return txn.rawInsert(
|
||||||
"""INSERT OR REPLACE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
"""INSERT OR REPLACE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||||
description, feed_id, milliseconds, duration, explicit, media_id) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
description, feed_id, milliseconds, duration, explicit, media_id, chapter_link,
|
||||||
|
episode_image) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||||
[
|
[
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
|
@ -632,7 +664,9 @@ class DBHelper {
|
||||||
milliseconds,
|
milliseconds,
|
||||||
duration,
|
duration,
|
||||||
explicit,
|
explicit,
|
||||||
url
|
url,
|
||||||
|
chapter,
|
||||||
|
image
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -686,12 +720,15 @@ class DBHelper {
|
||||||
final milliseconds = date.millisecondsSinceEpoch;
|
final milliseconds = date.millisecondsSinceEpoch;
|
||||||
final duration = item.itunes.duration?.inSeconds ?? 0;
|
final duration = item.itunes.duration?.inSeconds ?? 0;
|
||||||
final explicit = _getExplicit(item.itunes.explicit);
|
final explicit = _getExplicit(item.itunes.explicit);
|
||||||
|
final chapter = item.podcastChapters?.url ?? '';
|
||||||
|
final image = item.itunes.image.href;
|
||||||
|
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
await dbClient.transaction((txn) async {
|
await dbClient.transaction((txn) async {
|
||||||
await txn.rawInsert(
|
await txn.rawInsert(
|
||||||
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
"""INSERT OR IGNORE INTO Episodes(title, enclosure_url, enclosure_length, pubDate,
|
||||||
description, feed_id, milliseconds, duration, explicit, media_id, is_new) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)""",
|
description, feed_id, milliseconds, duration, explicit, media_id, chapter_link,
|
||||||
|
episode_image, is_new) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)""",
|
||||||
[
|
[
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
|
@ -703,6 +740,8 @@ class DBHelper {
|
||||||
duration,
|
duration,
|
||||||
explicit,
|
explicit,
|
||||||
url,
|
url,
|
||||||
|
chapter,
|
||||||
|
image
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1611,13 +1650,30 @@ class DBHelper {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String> getChapter(String url) async {
|
||||||
|
var dbClient = await database;
|
||||||
|
List<Map> list = await dbClient.rawQuery(
|
||||||
|
'SELECT chapter_link FROM Episodes WHERE enclosure_url = ?', [url]);
|
||||||
|
String chapter = list[0]['chapter_link'];
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getEpisodeImage(String url) async {
|
||||||
|
var dbClient = await database;
|
||||||
|
List<Map> list = await dbClient.rawQuery(
|
||||||
|
'SELECT episode_image FROM Episodes WHERE enclosure_url = ?', [url]);
|
||||||
|
String image = list[0]['episode_image'];
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
Future<EpisodeBrief> getRssItemWithUrl(String url) async {
|
Future<EpisodeBrief> getRssItemWithUrl(String url) async {
|
||||||
var dbClient = await database;
|
var dbClient = await database;
|
||||||
EpisodeBrief episode;
|
EpisodeBrief episode;
|
||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||||
P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end,
|
P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end,
|
||||||
E.is_new, P.primaryColor, E.media_id FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
E.is_new, P.primaryColor, E.media_id, E.episode_image, E.chapter_link
|
||||||
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
WHERE E.enclosure_url = ?""", [url]);
|
WHERE E.enclosure_url = ?""", [url]);
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1635,7 +1691,9 @@ class DBHelper {
|
||||||
list.first['is_new'],
|
list.first['is_new'],
|
||||||
mediaId: list.first['media_id'],
|
mediaId: list.first['media_id'],
|
||||||
skipSecondsStart: list.first['skip_seconds'],
|
skipSecondsStart: list.first['skip_seconds'],
|
||||||
skipSecondsEnd: list.first['skip_seconds_end']);
|
skipSecondsEnd: list.first['skip_seconds_end'],
|
||||||
|
episodeImage: list.first['episode_image'],
|
||||||
|
chapterLink: list.first['chapter_link']);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1646,8 +1704,9 @@ class DBHelper {
|
||||||
List<Map> list = await dbClient.rawQuery(
|
List<Map> list = await dbClient.rawQuery(
|
||||||
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
"""SELECT E.title, E.enclosure_url, E.enclosure_length, E.milliseconds, P.imagePath,
|
||||||
P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end,
|
P.title as feed_title, E.duration, E.explicit, P.skip_seconds, P.skip_seconds_end,
|
||||||
E.is_new, P.primaryColor, E.media_id FROM Episodes E INNER JOIN
|
E.is_new, P.primaryColor, E.media_id, E.episode_image, E.chapter_link
|
||||||
PodcastLocal P ON E.feed_id = P.id WHERE E.media_id = ?""", [id]);
|
FROM Episodes E INNER JOIN PodcastLocal P ON E.feed_id = P.id
|
||||||
|
WHERE E.media_id = ?""", [id]);
|
||||||
if (list.isEmpty) {
|
if (list.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1664,7 +1723,9 @@ class DBHelper {
|
||||||
list.first['is_new'],
|
list.first['is_new'],
|
||||||
mediaId: list.first['media_id'],
|
mediaId: list.first['media_id'],
|
||||||
skipSecondsStart: list.first['skip_seconds'],
|
skipSecondsStart: list.first['skip_seconds'],
|
||||||
skipSecondsEnd: list.first['skip_seconds_end']);
|
skipSecondsEnd: list.first['skip_seconds_end'],
|
||||||
|
episodeImage: list.first['episode_image'],
|
||||||
|
chapterLink: list.first['chapter_link']);
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
class ChapterInfo {
|
||||||
|
List<Chapters> chapters;
|
||||||
|
String version;
|
||||||
|
|
||||||
|
ChapterInfo({this.chapters, this.version});
|
||||||
|
|
||||||
|
ChapterInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['chapters'] != null) {
|
||||||
|
chapters = <Chapters>[];
|
||||||
|
json['chapters'].forEach((v) {
|
||||||
|
chapters.add(Chapters.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
version = json['version'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Chapters {
|
||||||
|
String img;
|
||||||
|
int startTime;
|
||||||
|
String title;
|
||||||
|
String url;
|
||||||
|
|
||||||
|
Chapters({this.img, this.startTime, this.title, this.url});
|
||||||
|
|
||||||
|
Chapters.fromJson(Map<String, dynamic> json) {
|
||||||
|
img = json['img'];
|
||||||
|
startTime = json['startTime'];
|
||||||
|
title = json['title'];
|
||||||
|
url = json['url'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final data = <String, dynamic>{};
|
||||||
|
data['img'] = img;
|
||||||
|
data['startTime'] = startTime;
|
||||||
|
data['title'] = title;
|
||||||
|
data['url'] = url;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ class EpisodeBrief extends Equatable {
|
||||||
final int skipSecondsStart;
|
final int skipSecondsStart;
|
||||||
final int skipSecondsEnd;
|
final int skipSecondsEnd;
|
||||||
final int downloadDate;
|
final int downloadDate;
|
||||||
|
final String episodeImage;
|
||||||
|
final String chapterLink;
|
||||||
EpisodeBrief(
|
EpisodeBrief(
|
||||||
this.title,
|
this.title,
|
||||||
this.enclosureUrl,
|
this.enclosureUrl,
|
||||||
|
@ -41,7 +43,9 @@ class EpisodeBrief extends Equatable {
|
||||||
this.skipSecondsStart,
|
this.skipSecondsStart,
|
||||||
this.skipSecondsEnd,
|
this.skipSecondsEnd,
|
||||||
this.description = '',
|
this.description = '',
|
||||||
this.downloadDate = 0})
|
this.downloadDate = 0,
|
||||||
|
this.chapterLink = '',
|
||||||
|
this.episodeImage = ''})
|
||||||
: assert(enclosureUrl != null);
|
: assert(enclosureUrl != null);
|
||||||
|
|
||||||
MediaItem toMediaItem() {
|
MediaItem toMediaItem() {
|
||||||
|
|
|
@ -1242,7 +1242,13 @@ class TabIndicator extends CustomPainter {
|
||||||
canvas.drawLine(leftStart, leftEnd,
|
canvas.drawLine(leftStart, leftEnd,
|
||||||
index == 0 || fraction == 0 ? _accentPaint : _paint);
|
index == 0 || fraction == 0 ? _accentPaint : _paint);
|
||||||
canvas.drawLine(rightStart, rightEnd,
|
canvas.drawLine(rightStart, rightEnd,
|
||||||
index == 1 || fraction == 0 ? _accentPaint : _paint);
|
index == 2 || fraction == 0 ? _accentPaint : _paint);
|
||||||
|
if (fraction == 1) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.width/2 - indicatorSize / 2, size.height),
|
||||||
|
Offset(size.width/2 + indicatorSize / 2, size.height),
|
||||||
|
index == 1 || fraction == 0 ? _accentPaint : _paint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1466,7 +1472,8 @@ class CircleProgressIndicator extends CustomPainter {
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(Canvas canvas, Size size) {
|
||||||
var center = Offset(size.width / 2, size.height / 2);
|
var center = Offset(size.width / 2, size.height / 2);
|
||||||
canvas.drawArc(
|
canvas.drawArc(
|
||||||
Rect.fromCenter(center: center, height: size.height*2, width: size.width*2),
|
Rect.fromCenter(
|
||||||
|
center: center, height: size.height * 2, width: size.width * 2),
|
||||||
-math.pi / 2,
|
-math.pi / 2,
|
||||||
math.pi * 2 * (progress / 100),
|
math.pi * 2 * (progress / 100),
|
||||||
true,
|
true,
|
||||||
|
|
|
@ -34,6 +34,7 @@ dependencies:
|
||||||
flutter_isolate: ^1.0.0+14
|
flutter_isolate: ^1.0.0+14
|
||||||
flutter_linkify: ^4.0.2
|
flutter_linkify: ^4.0.2
|
||||||
flutter_file_dialog: ^1.0.0
|
flutter_file_dialog: ^1.0.0
|
||||||
|
flutter_cache_manager: ^1.4.0
|
||||||
flare_flutter: ^2.0.6
|
flare_flutter: ^2.0.6
|
||||||
fl_chart: ^0.12.2
|
fl_chart: ^0.12.2
|
||||||
marquee: ^1.6.1
|
marquee: ^1.6.1
|
||||||
|
|
Loading…
Reference in New Issue