Add share clip feature
This commit is contained in:
parent
2f9af80c9c
commit
6ebe9a4a3c
11
README.md
11
README.md
|
@ -22,12 +22,13 @@ The podcasts search engine is powered by [ListenNotes](https://listennotes.com).
|
|||
## Features
|
||||
* Subscriptoin group management
|
||||
* Playlist support
|
||||
* Sleep timer
|
||||
* Sleep timer / Speed setting
|
||||
* OMPL file export and import
|
||||
* Auto syncing in background
|
||||
* Listen and subscribe history record
|
||||
* Dark mode / Accent color personalization
|
||||
* Download for offline playing
|
||||
* Dark mode / Accent color
|
||||
* Download for offline playing
|
||||
* Share clip on twitter
|
||||
|
||||
More to come...
|
||||
|
||||
|
@ -46,12 +47,14 @@ Tsacdop is using ListenNotes api 1.0 pro to search podcast, which is not free. S
|
|||
If you want to build the app, you need to create a new file named .env.dart in lib folder. Add below code in .env.dart.
|
||||
|
||||
```
|
||||
final environment = {"apiKey":"APIKEY"};
|
||||
final environment = {"apiKey":"APIKEY", "shareKey":"SHAREKEY"};
|
||||
```
|
||||
|
||||
You can get own api key on [RapidApi](https://rapidapi.com/listennotes/api/listennotes), basic plan is free to all, and replace "APIKEY" with it.
|
||||
If no api key added, the search function in the app won't work. But you can still add podcasts by serach rss link or import ompl file.
|
||||
|
||||
Share_key is used for generate clip.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
|
|
@ -47,7 +47,7 @@ android {
|
|||
applicationId "com.stonegate.tsacdop"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 11
|
||||
versionCode 12
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:tsacdop/util/custompaint.dart';
|
|||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
|
||||
const String version = '0.2.3';
|
||||
const String version = '0.2.4';
|
||||
|
||||
class AboutApp extends StatelessWidget {
|
||||
_launchUrl(String url) async {
|
||||
|
|
|
@ -19,6 +19,7 @@ import '../util/customslider.dart';
|
|||
import '../episodes/episodedetail.dart';
|
||||
import 'playlist.dart';
|
||||
import 'audiopanel.dart';
|
||||
import 'share.dart';
|
||||
|
||||
final List<BoxShadow> _customShadow = [
|
||||
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
||||
|
@ -54,7 +55,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
return Container(
|
||||
alignment: Alignment.topLeft,
|
||||
height: 300,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
|
@ -305,7 +306,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
Widget _expandedPanel(BuildContext context) {
|
||||
return DefaultTabController(
|
||||
initialIndex: 1,
|
||||
length: 3,
|
||||
length: 4,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
TabBarView(
|
||||
|
@ -313,6 +314,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
SleepMode(),
|
||||
ControlPanel(),
|
||||
_playlist(context),
|
||||
ShareClip(),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
|
@ -364,6 +366,16 @@ class _PlayerWidgetState extends State<PlayerWidget> {
|
|||
color: Theme.of(context).accentColor,
|
||||
width: 2.0)),
|
||||
),
|
||||
Container(
|
||||
height: 8.0,
|
||||
width: 8.0,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.transparent,
|
||||
border: Border.all(
|
||||
color: Theme.of(context).accentColor,
|
||||
width: 2.0)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
import '../util/context_extension.dart';
|
||||
|
||||
class ClipPreview extends StatefulWidget {
|
||||
final String filePath;
|
||||
ClipPreview({this.filePath, Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ClipPreviewState createState() => _ClipPreviewState();
|
||||
}
|
||||
|
||||
class _ClipPreviewState extends State<ClipPreview> {
|
||||
VideoPlayerController _controller;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = VideoPlayerController.file(File(widget.filePath))
|
||||
..initialize().then((_) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
systemNavigationBarColor: Colors.black,
|
||||
systemNavigationBarIconBrightness: Brightness.dark,
|
||||
),
|
||||
child: Scaffold(
|
||||
appBar: AppBar(backgroundColor: Colors.black),
|
||||
backgroundColor: Colors.black,
|
||||
body: Center(
|
||||
child: _controller.value.initialized
|
||||
? AspectRatio(
|
||||
aspectRatio: _controller.value.aspectRatio,
|
||||
child: VideoPlayer(_controller),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
backgroundColor: context.accentColor,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_controller.value.isPlaying
|
||||
? _controller.pause()
|
||||
: _controller.play();
|
||||
});
|
||||
},
|
||||
child: Icon(
|
||||
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,469 @@
|
|||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:tsacdop/home/preview.dart';
|
||||
import 'package:wc_flutter_share/wc_flutter_share.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../state/audiostate.dart';
|
||||
import '../util/context_extension.dart';
|
||||
import '../util/customslider.dart';
|
||||
import '../util/pageroute.dart';
|
||||
|
||||
final List<BoxShadow> _customShadow = [
|
||||
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),
|
||||
BoxShadow(
|
||||
blurRadius: 8,
|
||||
offset: Offset(2, 2),
|
||||
color: Colors.grey[600].withOpacity(0.4))
|
||||
];
|
||||
|
||||
final List<BoxShadow> _customShadowNight = [
|
||||
BoxShadow(
|
||||
blurRadius: 6,
|
||||
offset: Offset(-1, -1),
|
||||
color: Colors.grey[100].withOpacity(0.3)),
|
||||
BoxShadow(blurRadius: 8, offset: Offset(2, 2), color: Colors.black)
|
||||
];
|
||||
|
||||
String _stringForSeconds(int seconds) {
|
||||
if (seconds == null) return null;
|
||||
return '${(seconds ~/ 60)}:${(seconds.truncate() % 60).toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
class ShareClip extends StatefulWidget {
|
||||
ShareClip({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ShareClipState createState() => _ShareClipState();
|
||||
}
|
||||
|
||||
class _ShareClipState extends State<ShareClip> {
|
||||
int _durationSelected;
|
||||
int _startPosition;
|
||||
bool _startConfirm;
|
||||
List<int> _durationToSelect = [30, 60, 90, 120];
|
||||
Widget _animatedWidget;
|
||||
Widget _toastWidget;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_durationSelected = 60;
|
||||
_startPosition = 0;
|
||||
_startConfirm = false;
|
||||
_animatedWidget = Center();
|
||||
_toastWidget = Center();
|
||||
}
|
||||
|
||||
_formatSeconds(int s) {
|
||||
switch (s) {
|
||||
case 30:
|
||||
return "30sec";
|
||||
break;
|
||||
case 60:
|
||||
return "1min";
|
||||
break;
|
||||
case 90:
|
||||
return "90sec";
|
||||
break;
|
||||
case 120:
|
||||
return "2min";
|
||||
break;
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_setShareButton(ShareStatus status, String filePath) {
|
||||
switch (status) {
|
||||
case ShareStatus.generate:
|
||||
_animatedWidget = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
)),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
Text('Clipping'),
|
||||
],
|
||||
);
|
||||
_toastWidget = Text('May take one minute',
|
||||
style: TextStyle(color: const Color(0xff67727d)));
|
||||
break;
|
||||
case ShareStatus.download:
|
||||
_animatedWidget = Text('Loading');
|
||||
break;
|
||||
case ShareStatus.complete:
|
||||
_animatedWidget = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.share),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
Text('Share'),
|
||||
],
|
||||
);
|
||||
_toastWidget = Row(
|
||||
children: [
|
||||
Text('Preview'),
|
||||
IconButton(
|
||||
icon: Icon(LineIcons.play_solid),
|
||||
onPressed: () {
|
||||
if (filePath != '')
|
||||
Navigator.push(
|
||||
context,
|
||||
SlideLeftRoute(
|
||||
page: ClipPreview(
|
||||
filePath: filePath,
|
||||
)),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
break;
|
||||
case ShareStatus.undefined:
|
||||
_animatedWidget = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(LineIcons.cut_solid),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2),
|
||||
),
|
||||
Text('Clip')
|
||||
],
|
||||
);
|
||||
_toastWidget = Center();
|
||||
break;
|
||||
case ShareStatus.error:
|
||||
_animatedWidget = Text('Retry');
|
||||
_toastWidget = Text('Something wrong happened');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
|
||||
return Container(
|
||||
height: 300,
|
||||
width: double.infinity,
|
||||
color: context.primaryColor,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
height: 60.0,
|
||||
// color: context.primaryColorDark,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20.0),
|
||||
height: 20.0,
|
||||
// color: context.primaryColorDark,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
'Share Clip',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).accentColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Selector<AudioPlayerNotifier, Tuple2<ShareStatus, String>>(
|
||||
selector: (_, audio) =>
|
||||
Tuple2(audio.shareStatus, audio.shareFile ?? ''),
|
||||
builder: (_, data, __) {
|
||||
_setShareButton(data.item1, data.item2);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
transitionBuilder: (child, animation) =>
|
||||
ScaleTransition(scale: animation, child: child),
|
||||
duration: Duration(milliseconds: 500),
|
||||
child: _toastWidget),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
height: 40,
|
||||
width: 120,
|
||||
decoration: BoxDecoration(
|
||||
color: context.primaryColor,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(20)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? Colors.black12
|
||||
: Colors.white10,
|
||||
width: 1),
|
||||
boxShadow: Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? _customShadowNight
|
||||
: _customShadow),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(15)),
|
||||
onTap: () async {
|
||||
if (data.item1 == ShareStatus.undefined ||
|
||||
data.item1 ==
|
||||
ShareStatus.error) if (_startConfirm)
|
||||
audio.shareClip(
|
||||
_startPosition, _durationSelected);
|
||||
else
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Please confirm start position',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
else if (data.item1 == ShareStatus.complete) {
|
||||
File file = File(data.item2);
|
||||
final Uint8List bytes =
|
||||
await file.readAsBytes();
|
||||
await WcFlutterShare.share(
|
||||
sharePopupTitle: 'share Clip',
|
||||
fileName: data.item2.split('/').last,
|
||||
mimeType: 'video/mp4',
|
||||
bytesOfFile: bytes.buffer.asUint8List());
|
||||
audio.setShareStatue = ShareStatus.undefined;
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
width: 100,
|
||||
child: Center(
|
||||
child: AnimatedSwitcher(
|
||||
transitionBuilder: (child, animation) =>
|
||||
ScaleTransition(
|
||||
scale: animation, child: child),
|
||||
duration: Duration(milliseconds: 700),
|
||||
child: _animatedWidget)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Consumer<AudioPlayerNotifier>(builder: (_, data, __) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 5, left: 10, right: 10),
|
||||
child: SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
activeTrackColor:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black38
|
||||
: Colors.grey[400],
|
||||
inactiveTrackColor: Theme.of(context).primaryColorDark,
|
||||
trackHeight: 20.0,
|
||||
trackShape: MyRectangularTrackShape(),
|
||||
thumbColor: Theme.of(context).accentColor,
|
||||
thumbShape: MyRoundSliderThumpShape(
|
||||
enabledThumbRadius: 10.0,
|
||||
disabledThumbRadius: 10.0,
|
||||
thumbCenterColor: context.accentColor),
|
||||
overlayColor: Theme.of(context).accentColor.withAlpha(32),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 4.0),
|
||||
),
|
||||
child: Slider(
|
||||
value: data.seekSliderValue,
|
||||
onChanged: (double val) {
|
||||
audio.sliderSeek(val);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Expanded(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 150,
|
||||
width: 200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
child: Center(
|
||||
child: Selector<AudioPlayerNotifier, int>(
|
||||
selector: (_, audio) => audio.backgroundAudioPosition,
|
||||
builder: (_, position, __) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
text: 'Start at \n',
|
||||
style:
|
||||
TextStyle(color: context.accentColor),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: !_startConfirm
|
||||
? _stringForSeconds(
|
||||
position ~/ 1000)
|
||||
: _stringForSeconds(
|
||||
_startPosition),
|
||||
style: context.textTheme.headline5
|
||||
.copyWith(
|
||||
color: _startConfirm
|
||||
? context.accentColor
|
||||
: null)),
|
||||
]),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
onPressed: () => audio.forwardAudio(-30),
|
||||
iconSize: 25.0,
|
||||
icon: Icon(Icons.replay_30),
|
||||
color: Colors.grey[500]),
|
||||
InkWell(
|
||||
onTap: () => setState(() {
|
||||
if (!_startConfirm)
|
||||
_startPosition = position ~/ 1000;
|
||||
_startConfirm = !_startConfirm;
|
||||
}),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
boxShadow: !_startConfirm
|
||||
? (context.brightness ==
|
||||
Brightness.dark)
|
||||
? _customShadowNight
|
||||
: _customShadow
|
||||
: null,
|
||||
color: _startConfirm
|
||||
? Theme.of(context).accentColor
|
||||
: Theme.of(context).primaryColor,
|
||||
shape: BoxShape.circle),
|
||||
alignment: Alignment.center,
|
||||
width: 40,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Icon(
|
||||
LineIcons.thumbtack_solid,
|
||||
color: _startConfirm
|
||||
? Colors.white
|
||||
: null,
|
||||
)),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 10.0),
|
||||
onPressed: () => audio.forwardAudio(10),
|
||||
iconSize: 25.0,
|
||||
icon: Icon(Icons.forward_10),
|
||||
color: Colors.grey[500]),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 100,
|
||||
width: 2,
|
||||
color: context.primaryColorDark,
|
||||
),
|
||||
SizedBox(
|
||||
height: 150,
|
||||
width: 200,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: Center(
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
children: _durationToSelect
|
||||
.map<Widget>((e) => InkWell(
|
||||
onTap: () =>
|
||||
setState(() => _durationSelected = e),
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(10.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(15)),
|
||||
boxShadow: e != _durationSelected
|
||||
? (context.brightness ==
|
||||
Brightness.dark)
|
||||
? _customShadowNight
|
||||
: _customShadow
|
||||
: null,
|
||||
color: (e == _durationSelected)
|
||||
? Theme.of(context).accentColor
|
||||
: Theme.of(context).primaryColor,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
width: 70,
|
||||
height: 30,
|
||||
child: Text(_formatSeconds(e),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: (e == _durationSelected)
|
||||
? Colors.white
|
||||
: null)),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 20,
|
||||
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Text('experimental'),
|
||||
Icon(
|
||||
LineIcons.info_circle_solid,
|
||||
size: 20.0,
|
||||
color: context.accentColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1039,4 +1039,14 @@ class DBHelper {
|
|||
return episode;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return list.first["imageUrl"];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,4 +68,5 @@ List<Libries> plugins = [
|
|||
Libries('Rxdart', apacheLicense, 'https://pub.dev/packages/rxdart'),
|
||||
Libries('flutter_isolate', mit, 'https://pub.dev/packages/flutter_isolate'),
|
||||
Libries('auto_animated', mit, 'https://pub.dev/packages/auto_animated'),
|
||||
Libries('wc_flutter_share', apacheLicense, 'https://pub.dev/packages/wc_flutter_share')
|
||||
];
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:path/path.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../type/episodebrief.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../.env.dart';
|
||||
|
||||
MediaControl playControl = MediaControl(
|
||||
androidIcon: 'drawable/ic_stat_play_circle_filled',
|
||||
|
@ -110,6 +117,7 @@ class Playlist {
|
|||
}
|
||||
|
||||
enum SleepTimerMode { endOfEpisode, timer, undefined }
|
||||
enum ShareStatus { generate, download, complete, undefined, error }
|
||||
|
||||
class AudioPlayerNotifier extends ChangeNotifier {
|
||||
DBHelper dbHelper = DBHelper();
|
||||
|
@ -135,6 +143,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
bool _startSleepTimer = false;
|
||||
double _switchValue = 0;
|
||||
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
|
||||
ShareStatus _shareStatus = ShareStatus.undefined;
|
||||
String _shareFile = '';
|
||||
//set autoplay episode in playlist
|
||||
bool _autoPlay = true;
|
||||
//TODO Set auto add episodes to playlist
|
||||
|
@ -158,6 +168,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
bool get stopOnComplete => _stopOnComplete;
|
||||
bool get startSleepTimer => _startSleepTimer;
|
||||
SleepTimerMode get sleepTimerMode => _sleepTimerMode;
|
||||
ShareStatus get shareStatus => _shareStatus;
|
||||
String get shareFile => _shareFile;
|
||||
bool get autoPlay => _autoPlay;
|
||||
int get timeLeft => _timeLeft;
|
||||
double get switchValue => _switchValue;
|
||||
|
@ -174,6 +186,11 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
_setAutoPlay();
|
||||
}
|
||||
|
||||
set setShareStatue(ShareStatus status) {
|
||||
_shareStatus = status;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future _getAutoPlay() async {
|
||||
int i = await autoPlayStorage.getInt();
|
||||
_autoPlay = i == 0 ? true : false;
|
||||
|
@ -556,6 +573,48 @@ class AudioPlayerNotifier extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
shareClip(int start, int duration) async {
|
||||
_shareStatus = ShareStatus.generate;
|
||||
notifyListeners();
|
||||
int length = math.min(duration, (_backgroundAudioDuration ~/ 1000 - start));
|
||||
final BaseOptions options = BaseOptions(
|
||||
connectTimeout: 60000,
|
||||
receiveTimeout: 120000,
|
||||
);
|
||||
String imageUrl = await dbHelper.getImageUrl(_episode.enclosureUrl);
|
||||
String url = "https://podcastapi.stonegate.me/clip?" +
|
||||
"audio_link=${_episode.enclosureUrl}&image_link=$imageUrl&title=${_episode.feedTitle}" +
|
||||
"&text=${_episode.title}&start=$start&length=$length";
|
||||
String shareKey = environment['shareKey'];
|
||||
try {
|
||||
Response response = await Dio(options).get(url,
|
||||
options: Options(headers: {
|
||||
'X-Share-Key': "$shareKey",
|
||||
}));
|
||||
String shareLink = response.data;
|
||||
print(shareLink);
|
||||
String fileName = _episode.title + start.toString() + '.mp4';
|
||||
_shareStatus = ShareStatus.download;
|
||||
notifyListeners();
|
||||
Directory dir = await getTemporaryDirectory();
|
||||
String shareDir = join(dir.path, 'share', fileName);
|
||||
try {
|
||||
await Dio().download(shareLink, shareDir);
|
||||
_shareFile = shareDir;
|
||||
_shareStatus = ShareStatus.complete;
|
||||
notifyListeners();
|
||||
} on DioError catch (e) {
|
||||
print(e);
|
||||
_shareStatus = ShareStatus.error;
|
||||
notifyListeners();
|
||||
}
|
||||
} catch (e) {
|
||||
print(e);
|
||||
_shareStatus = ShareStatus.error;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
await AudioService.stop();
|
||||
|
|
|
@ -46,16 +46,18 @@ class MyRoundSliderThumpShape extends SliderComponentShape {
|
|||
@required SliderThemeData sliderTheme,
|
||||
TextDirection textDirection,
|
||||
double value,
|
||||
double textScaleFactor,
|
||||
Size sizeWithOverflow,
|
||||
}) {
|
||||
final Canvas canvas = context.canvas;
|
||||
final Tween<double> radiusTween = Tween<double>(
|
||||
begin: _disabledThumbRadius,
|
||||
end: enabledThumbRadius,
|
||||
);
|
||||
// final ColorTween colorTween = ColorTween(
|
||||
// begin: sliderTheme.disabledThumbColor,
|
||||
// end: sliderTheme.thumbColor,
|
||||
// );
|
||||
final ColorTween colorTween = ColorTween(
|
||||
begin: sliderTheme.disabledThumbColor,
|
||||
end: sliderTheme.thumbColor,
|
||||
);
|
||||
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: tsacdop
|
||||
description: An easy-use podacasts player.
|
||||
|
||||
version: 0.2.3
|
||||
version: 0.2.4
|
||||
|
||||
environment:
|
||||
sdk: ">=2.6.0 <3.0.0"
|
||||
|
@ -47,6 +47,8 @@ dev_dependencies:
|
|||
flare_flutter: ^2.0.3
|
||||
rxdart: ^0.24.0
|
||||
auto_animated: ^2.1.0
|
||||
wc_flutter_share: ^0.2.1
|
||||
video_player: ^0.10.11
|
||||
just_audio:
|
||||
git:
|
||||
url: https://github.com/stonega/just_audio.git
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||
Future<void> main() async {
|
||||
final config = {
|
||||
'apiKey': Platform.environment['API_KEY'],
|
||||
'shareKey': Platform.environment['SHARE_KEY']
|
||||
};
|
||||
|
||||
final filename = 'lib/.env.dart';
|
||||
|
|
Loading…
Reference in New Issue