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,
|
||||
Tuple2<bool, PlayerHeight>>(
|
||||
selector: (_, audio) => Tuple2(
|
||||
|
@ -580,9 +580,9 @@ class __MenuBarState extends State<_MenuBar> {
|
|||
}
|
||||
}
|
||||
|
||||
class _ShowNote extends StatelessWidget {
|
||||
class ShowNote extends StatelessWidget {
|
||||
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) {
|
||||
final time = url.substring(3).trim();
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
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:google_fonts/google_fonts.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:marquee/marquee.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
||||
import '../episodes/episode_detail.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../playlists/playlist_home.dart';
|
||||
import '../state/audio_state.dart';
|
||||
import '../type/chapter.dart';
|
||||
import '../type/episodebrief.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/playlist.dart';
|
||||
|
@ -425,6 +431,7 @@ class _PlaylistWidgetState extends State<PlaylistWidget> {
|
|||
children: <Widget>[
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (context, index) {
|
||||
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 {
|
||||
ControlPanel(
|
||||
{this.onExpand,
|
||||
|
@ -938,7 +1219,7 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
@override
|
||||
void initState() {
|
||||
_setSpeed = 0;
|
||||
_tabController = TabController(vsync: this, length: 2)
|
||||
_tabController = TabController(vsync: this, length: 3)
|
||||
..addListener(() {
|
||||
setState(() => _tabIndex = _tabController.index);
|
||||
});
|
||||
|
@ -1262,13 +1543,16 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0),
|
||||
child: PlaylistWidget(),
|
||||
),
|
||||
child: PlaylistWidget()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0),
|
||||
child: SleepMode(),
|
||||
)
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20.0),
|
||||
child: ChaptersWidget()),
|
||||
]),
|
||||
))),
|
||||
),
|
||||
|
@ -1438,14 +1722,14 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
child: InkWell(
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
width: 100,
|
||||
width: 115,
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: CustomPaint(
|
||||
size: Size(100, 5),
|
||||
size: Size(120, 5),
|
||||
painter: TabIndicator(
|
||||
index: _tabIndex,
|
||||
indicatorSize: 20,
|
||||
indicatorSize: 10,
|
||||
fraction:
|
||||
(height + 16 - widget.maxHeight) /
|
||||
(context.height -
|
||||
|
@ -1476,17 +1760,21 @@ class _ControlPanelState extends State<ControlPanel>
|
|||
unselectedLabelColor: context.textColor,
|
||||
indicator: BoxDecoration(),
|
||||
tabs: [
|
||||
Container(
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: Icon(Icons.playlist_play)),
|
||||
Container(
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: Transform.rotate(
|
||||
angle: math.pi * 0.7,
|
||||
child:
|
||||
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 path = join(documentsDirectory, "podcasts.db");
|
||||
var theDb = await openDatabase(path,
|
||||
version: 5, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||
version: 6, onCreate: _onCreate, onUpgrade: _onUpgrade);
|
||||
return theDb;
|
||||
}
|
||||
|
||||
|
@ -47,52 +47,83 @@ class DBHelper {
|
|||
description TEXT, feed_id TEXT, feed_link TEXT, milliseconds INTEGER,
|
||||
duration INTEGER DEFAULT 0, explicit INTEGER DEFAULT 0, liked INTEGER DEFAULT 0,
|
||||
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(
|
||||
"""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)""");
|
||||
await db.execute(
|
||||
"""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)""");
|
||||
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 {
|
||||
switch (oldVersion) {
|
||||
case (1):
|
||||
await db.execute(
|
||||
"ALTER TABLE PodcastLocal ADD skip_seconds INTEGER DEFAULT 0 ");
|
||||
await db.execute(
|
||||
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
|
||||
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 ");
|
||||
await db
|
||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
||||
await _v2Update(db);
|
||||
await _v3Update(db);
|
||||
await _v4Update(db);
|
||||
await _v5Update(db);
|
||||
await _v6Update(db);
|
||||
break;
|
||||
case (2):
|
||||
await db.execute(
|
||||
"ALTER TABLE PodcastLocal ADD auto_download INTEGER DEFAULT 0");
|
||||
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 ");
|
||||
await db
|
||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
||||
await _v3Update(db);
|
||||
await _v4Update(db);
|
||||
await _v5Update(db);
|
||||
await _v6Update(db);
|
||||
break;
|
||||
case (3):
|
||||
await _v4Update(db);
|
||||
await _v5Update(db);
|
||||
await _v6Update(db);
|
||||
break;
|
||||
case (4):
|
||||
await _v5Update(db);
|
||||
await _v6Update(db);
|
||||
break;
|
||||
case (5):
|
||||
await _v6Update(db);
|
||||
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 ");
|
||||
await db
|
||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
||||
break;
|
||||
case (4):
|
||||
await db
|
||||
.execute("ALTER TABLE PodcastLocal ADD funding TEXT DEFAULT '[]' ");
|
||||
break;
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -321,8 +352,7 @@ class DBHelper {
|
|||
Future<void> updatePodcastImage({String id, String filePath}) async {
|
||||
var dbClient = await database;
|
||||
return await dbClient.rawUpdate(
|
||||
"UPDATE PodcastLocal SET imagePath= ? WHERE id = ?",
|
||||
[filePath, id]);
|
||||
"UPDATE PodcastLocal SET imagePath= ? WHERE id = ?", [filePath, id]);
|
||||
}
|
||||
|
||||
Future<int> saveFiresideData(List<String> list) async {
|
||||
|
@ -616,12 +646,14 @@ class DBHelper {
|
|||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = feed.items[i].itunes.duration?.inSeconds ?? 0;
|
||||
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) {
|
||||
await dbClient.transaction((txn) {
|
||||
return txn.rawInsert(
|
||||
"""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,
|
||||
url,
|
||||
|
@ -632,7 +664,9 @@ class DBHelper {
|
|||
milliseconds,
|
||||
duration,
|
||||
explicit,
|
||||
url
|
||||
url,
|
||||
chapter,
|
||||
image
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
@ -686,12 +720,15 @@ class DBHelper {
|
|||
final milliseconds = date.millisecondsSinceEpoch;
|
||||
final duration = item.itunes.duration?.inSeconds ?? 0;
|
||||
final explicit = _getExplicit(item.itunes.explicit);
|
||||
final chapter = item.podcastChapters?.url ?? '';
|
||||
final image = item.itunes.image.href;
|
||||
|
||||
if (url != null) {
|
||||
await dbClient.transaction((txn) async {
|
||||
await txn.rawInsert(
|
||||
"""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,
|
||||
url,
|
||||
|
@ -703,6 +740,8 @@ class DBHelper {
|
|||
duration,
|
||||
explicit,
|
||||
url,
|
||||
chapter,
|
||||
image
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
@ -1611,13 +1650,30 @@ class DBHelper {
|
|||
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 {
|
||||
var dbClient = await database;
|
||||
EpisodeBrief episode;
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
"""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,
|
||||
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]);
|
||||
if (list.isEmpty) {
|
||||
return null;
|
||||
|
@ -1635,7 +1691,9 @@ class DBHelper {
|
|||
list.first['is_new'],
|
||||
mediaId: list.first['media_id'],
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1646,8 +1704,9 @@ class DBHelper {
|
|||
List<Map> list = await dbClient.rawQuery(
|
||||
"""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,
|
||||
E.is_new, P.primaryColor, E.media_id FROM Episodes E INNER JOIN
|
||||
PodcastLocal P ON E.feed_id = P.id WHERE E.media_id = ?""", [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.media_id = ?""", [id]);
|
||||
if (list.isEmpty) {
|
||||
return null;
|
||||
} else {
|
||||
|
@ -1664,7 +1723,9 @@ class DBHelper {
|
|||
list.first['is_new'],
|
||||
mediaId: list.first['media_id'],
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 skipSecondsEnd;
|
||||
final int downloadDate;
|
||||
final String episodeImage;
|
||||
final String chapterLink;
|
||||
EpisodeBrief(
|
||||
this.title,
|
||||
this.enclosureUrl,
|
||||
|
@ -41,7 +43,9 @@ class EpisodeBrief extends Equatable {
|
|||
this.skipSecondsStart,
|
||||
this.skipSecondsEnd,
|
||||
this.description = '',
|
||||
this.downloadDate = 0})
|
||||
this.downloadDate = 0,
|
||||
this.chapterLink = '',
|
||||
this.episodeImage = ''})
|
||||
: assert(enclosureUrl != null);
|
||||
|
||||
MediaItem toMediaItem() {
|
||||
|
|
|
@ -1242,8 +1242,14 @@ class TabIndicator extends CustomPainter {
|
|||
canvas.drawLine(leftStart, leftEnd,
|
||||
index == 0 || fraction == 0 ? _accentPaint : _paint);
|
||||
canvas.drawLine(rightStart, rightEnd,
|
||||
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
|
||||
bool shouldRepaint(TabIndicator oldDelegate) {
|
||||
|
@ -1466,7 +1472,8 @@ class CircleProgressIndicator extends CustomPainter {
|
|||
void paint(Canvas canvas, Size size) {
|
||||
var center = Offset(size.width / 2, size.height / 2);
|
||||
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 * (progress / 100),
|
||||
true,
|
||||
|
|
|
@ -34,6 +34,7 @@ dependencies:
|
|||
flutter_isolate: ^1.0.0+14
|
||||
flutter_linkify: ^4.0.2
|
||||
flutter_file_dialog: ^1.0.0
|
||||
flutter_cache_manager: ^1.4.0
|
||||
flare_flutter: ^2.0.6
|
||||
fl_chart: ^0.12.2
|
||||
marquee: ^1.6.1
|
||||
|
|
Loading…
Reference in New Issue