diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 31de08e..953a07c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ --> + diff --git a/lib/config/application.dart b/lib/config/application.dart index 3fbb01d..f389f65 100644 --- a/lib/config/application.dart +++ b/lib/config/application.dart @@ -9,30 +9,43 @@ import 'package:sqflite/sqflite.dart'; import '../models/Category.dart'; import '../models/CaptureModel.dart'; import '../models/Series.dart'; +import '../models/Settings.dart'; class Application { static Router router; static Database db; static bool _dbIsOpened = false; static String _databaseName = 'cantonfair.db'; - static Map cache; + static Map cache; static String databasePath = ""; static String appDir; + static String mainDir; static String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); static Future backupDatabase() async { await closeDatabase(); File f = new File(join(databasePath, _databaseName)); - String newPath = join(databasePath, '${timestamp()}.db'); + var backup = join(appDir, "Backup"); + String newPath = join(backup, '${timestamp()}.db'); + await Directory(backup).create(recursive: true); print("Coping to $newPath"); await f.copy(newPath); await openDB(); } - static Future initDatabase() async { - Directory extPath = await getExternalStorageDirectory(); - appDir = join(extPath.path, "CantonFair"); + static Future forceDelete() async { + await closeDatabase(); + File f = new File(join(databasePath, _databaseName)); + if (await f.exists()) await f.delete(); + await openDB(); + } + + static Future initDatabase() async { + Directory extDir = await getExternalStorageDirectory(); + mainDir = extDir.path; + appDir = join(mainDir, "CantonFair"); + await Directory(appDir).create(recursive: true); databasePath = join(appDir, "Database"); await openDB(); } @@ -47,15 +60,9 @@ class Application { print("Database opened !"); _dbIsOpened = true; - // only on development - // print("Droping ${Category.tableName} ..."); - // await db.execute("DROP TABLE ${Category.tableName};"); - - // print("Droping ${Series.tableName} ..."); - // await db.execute("DROP TABLE ${Series.tableName};"); - - // print("Droping ${CaptureModel.tableName} ..."); - // await db.execute("DROP TABLE ${CaptureModel.tableName};"); + print("Creating ${Settings.tableName} ..."); + await db.execute(Settings.dbOnCreate); + Settings.db = db; print("Creating ${CaptureModel.tableName} ..."); await db.execute(CaptureModel.dbOnCreate); @@ -71,11 +78,6 @@ class Application { }, onCreate: (Database db, int version) async { // only on production - print("Creating ${Category.tableName} ..."); - await db.execute(Category.dbOnCreate); - - print("Creating ${Series.tableName} ..."); - await db.execute(Series.dbOnCreate); }, ); } diff --git a/lib/models/Series.dart b/lib/models/Series.dart index 2e7731a..2a75f34 100644 --- a/lib/models/Series.dart +++ b/lib/models/Series.dart @@ -77,6 +77,7 @@ class Series { static Future getCategoryOfSeriesUUID(uuid, {inv: 'desc', order: 'created_at'}) async { var series = await getSelectedSeriesByUUID(uuid); + if (series == null) return null; var result = await db.rawQuery( 'SELECT * FROM ${Category.tableName} WHERE ${Category.dbUUID} = "${series.categoryUUID}" ORDER BY "$order $inv";'); List categories = []; diff --git a/lib/models/Settings.dart b/lib/models/Settings.dart new file mode 100644 index 0000000..e67a1e8 --- /dev/null +++ b/lib/models/Settings.dart @@ -0,0 +1,53 @@ +import 'dart:async'; +import 'package:sqflite/sqflite.dart'; + +class Settings { + static Database db; + + static final String dbKey = "key"; + static final String dbValue = "value"; + + static final String tableName = "settings"; + static final String dbOnCreate = "CREATE TABLE IF NOT EXISTS $tableName (" + "${Settings.dbKey} STRING PRIMARY KEY," + "${Settings.dbValue} Text" + ")"; + + String key; + String value; + + Settings({this.key, this.value}); + + Settings.fromMap(Map map) { + this.key = map[dbKey]; + this.value = map[dbValue]; + } + + Map toMap() { + return { + dbValue: value, + dbKey: key, + }; + } + + static Future fetch(String key) async { + var result = await db.rawQuery( + 'SELECT * FROM $tableName WHERE $dbKey = "$key";'); + List items = []; + for (Map item in result) { + items.add(new Settings.fromMap(item)); + } + return items.length > 0 ? items[0] : null; + } + + static Future save(Settings item) async { + await db.rawInsert( + 'INSERT OR REPLACE INTO ' + '$tableName(${Settings.dbKey}, ${Settings.dbValue})' + ' VALUES(?, ?)', + [ + item.key, + item.value, + ]); + } +} diff --git a/lib/pages/capture.dart b/lib/pages/capture.dart index cd135d1..2672f06 100644 --- a/lib/pages/capture.dart +++ b/lib/pages/capture.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:audio_recorder/audio_recorder.dart'; import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import '../config/application.dart'; import '../models/CaptureModel.dart'; @@ -32,13 +33,22 @@ class Choice { const Choice({this.title, this.icon, this.camera}); } -enum Options { video, videoRecording, photo, audio, audioRecording } +enum Options { + video, + videoRecording, + photo, + audio, + audioRecording, + hqVideo, + hqPhoto, +} class _CaptureRoute extends State { final GlobalKey _scaffoldKey = GlobalKey(); CameraController controller; CaptureModel model; + String openTime; var _state = Options.photo; bool _initializing = false; @@ -47,7 +57,9 @@ class _CaptureRoute extends State { bool _loading = false; final String uuid; - _CaptureRoute({this.uuid}); + _CaptureRoute({this.uuid}) { + openTime = timestamp(); + } @override Widget build(BuildContext context) { @@ -62,13 +74,6 @@ class _CaptureRoute extends State { ); } - @override - void initState() { - print("init state"); - _prepare(); - super.initState(); - } - @override void dispose() { print("capture dispose"); @@ -76,16 +81,18 @@ class _CaptureRoute extends State { this.controller?.dispose(); } + @override + void initState() { + _prepare(); + super.initState(); + } + void onAudioRecordButtonPressed() { - setState(() { - _loading = true; - }); + setState(() => _loading = true); startAudioRecord().then((String filePath) { _state = Options.audioRecording; if (mounted) { - setState(() { - _loading = false; - }); + setState(() => _loading = false); if (filePath != null) { model = CaptureModel( filePath: filePath, @@ -128,29 +135,90 @@ class _CaptureRoute extends State { } } + void onHQTakePhotoButtonPressed() { + setState(() => _loading = true); + hqTakePhoto().then((String filePath) { + if (mounted) { + _state = Options.hqPhoto; + setState(() => _loading = false); + if (filePath != null) { + model = CaptureModel( + filePath: filePath, + seriesUUID: uuid, + captureMode: CaptureMode.picture); + CaptureModel.updateItem(model); + } + setState(() {}); + } + }); + } + + void onHQVideoRecordButtonPressed() { + setState(() => _loading = true); + hqVideoRecord().then((String filePath) { + if (mounted) { + _state = Options.hqVideo; + setState(() => _loading = false); + if (filePath != null) { + model = CaptureModel( + filePath: filePath, + seriesUUID: uuid, + captureMode: CaptureMode.video); + CaptureModel.updateItem(model); + } + setState(() {}); + } + }); + } + + Future hqTakePhoto() async { + var image = await ImagePicker.pickImage(source: ImageSource.camera); + if (image == null) return null; + final String dirPath = + '${Application.appDir}/Categories/${_category.name}/${openTime}_$uuid/Photos'; + await Directory(dirPath).create(recursive: true); + final String filePath = '$dirPath/${timestamp()}.jpg'; + await image.copy(filePath); + await image.delete(); + return filePath; + } + + Future hqVideoRecord() async { + var video = await ImagePicker.pickVideo(source: ImageSource.camera); + if (video == null) return null; + final String dirPath = + '${Application.appDir}/Categories/${_category.name}/${openTime}_$uuid/Movies'; + await Directory(dirPath).create(recursive: true); + final String filePath = '$dirPath/${timestamp()}.mp4'; + await video.copy(filePath); + await video.delete(); + return filePath; + } + void onTakePictureButtonPressed() { - _loading = true; + setState(() => _loading = true); takePicture().then((String filePath) { if (mounted) { - _loading = false; + _state = Options.photo; + setState(() => _loading = false); if (filePath != null) { model = CaptureModel( filePath: filePath, seriesUUID: uuid, captureMode: CaptureMode.picture); CaptureModel.updateItem(model); - } + } setState(() {}); } }); } void onVideoRecordButtonPressed() { - _loading = true; + setState(() => _loading = true); startVideoRecording().then((String filePath) { if (mounted) { _state = Options.videoRecording; - _loading = false; + setState(() => _loading = false); if (filePath != null) { model = CaptureModel( filePath: filePath, @@ -170,9 +238,20 @@ class _CaptureRoute extends State { }); } + void showInSnackBar(String message) { + _scaffoldKey.currentState.showSnackBar( + SnackBar( + content: Text(message), + action: SnackBarAction( + label: 'HIDE', + onPressed: _scaffoldKey.currentState.hideCurrentSnackBar), + ), + ); + } + Future startAudioRecord() async { final String dirPath = - '${Application.appDir}/Categories/${_category.name}/$uuid/Audios'; + '${Application.appDir}/Categories/${_category.name}/${openTime}_$uuid/Audios'; await Directory(dirPath).create(recursive: true); final String filePath = '$dirPath/${timestamp()}.aac'; @@ -186,17 +265,6 @@ class _CaptureRoute extends State { return filePath; } - void showInSnackBar(String message) { - _scaffoldKey.currentState.showSnackBar( - SnackBar( - content: Text(message), - action: SnackBarAction( - label: 'HIDE', - onPressed: _scaffoldKey.currentState.hideCurrentSnackBar), - ), - ); - } - Future startVideoRecording() async { if (!controller.value.isInitialized) { showInSnackBar('Error: select a camera first.'); @@ -204,7 +272,7 @@ class _CaptureRoute extends State { } final String dirPath = - '${Application.appDir}/Categories/${_category.name}/$uuid/Movies'; + '${Application.appDir}/Categories/${_category.name}/${openTime}_$uuid/Movies'; await Directory(dirPath).create(recursive: true); final String filePath = '$dirPath/${timestamp()}.mp4'; @@ -247,7 +315,7 @@ class _CaptureRoute extends State { return null; } final String dirPath = - '${Application.appDir}/Categories/${_category.name}/$uuid/Pictures'; + '${Application.appDir}/Categories/${_category.name}/${openTime}_$uuid/Pictures'; await Directory(dirPath).create(recursive: true); final String filePath = '$dirPath/${timestamp()}.jpg'; @@ -294,6 +362,18 @@ class _CaptureRoute extends State { Options.audio, ), ), + IconButton( + color: whiteColor, + icon: Icon(Icons.ondemand_video), + onPressed: + _changeIfState(_state == Options.hqVideo, Options.hqVideo), + ), + IconButton( + color: whiteColor, + icon: Icon(Icons.camera_enhance), + onPressed: + _changeIfState(_state == Options.hqPhoto, Options.hqPhoto), + ), ], ), ), @@ -310,7 +390,7 @@ class _CaptureRoute extends State { } Function _captureButtonHandler() { - if (_loading) return null; + if (_loading || _initializing) return null; return () { if (_state == Options.photo) { onTakePictureButtonPressed(); @@ -322,6 +402,10 @@ class _CaptureRoute extends State { onAudioRecordButtonPressed(); } else if (_state == Options.audioRecording) { onAudioStopButtonPressed(); + } else if (_state == Options.hqVideo) { + onHQVideoRecordButtonPressed(); + } else if (_state == Options.hqPhoto) { + onHQTakePhotoButtonPressed(); } }; } @@ -338,15 +422,17 @@ class _CaptureRoute extends State { Widget _floatingButton() { return FloatingActionButton( - backgroundColor: secondaryColor, - foregroundColor: whiteColor, + backgroundColor: + (_state == Options.audioRecording || _state == Options.videoRecording) + ? whiteColor + : secondaryColor, child: _floatingButtonChild(), onPressed: _captureButtonHandler(), ); } Widget _floatingButtonChild() { - if (_loading) { + if (_loading || _initializing) { return Padding( padding: EdgeInsets.all(15.0), child: CircularProgressIndicator( @@ -357,8 +443,8 @@ class _CaptureRoute extends State { if (_state == Options.audioRecording || _state == Options.videoRecording) { return Icon( Icons.stop, - size: 50.0, - color: Colors.black.withRed(50), + size: 35.0, + color: Colors.redAccent, ); } return Icon(Icons.archive); @@ -373,12 +459,16 @@ class _CaptureRoute extends State { } void _prepare() async { - _initializing = true; + setState(() => _initializing = true); + await Future.delayed(Duration(milliseconds: 250)); _category = await Series.getCategoryOfSeriesUUID(uuid); + if (_category == null) { + showInSnackBar("Category not found !"); + return; + } await onNewCameraSelected(choices[0].camera); - await Future.delayed(Duration(milliseconds: 500)); - _initializing = false; - setState(() {}); + await Future.delayed(Duration(milliseconds: 250)); + setState(() => _initializing = false); } void _showCameraException(CameraException e) { diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 69fc36f..b10153c 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -204,6 +204,9 @@ class _HomeRoute extends State { ), ), ListTile( + onLongPress: () { + Application.router.navigateTo(context, '/categories'); + }, title: Text("Category"), leading: Icon(Icons.list), subtitle: diff --git a/lib/pages/item-view.dart b/lib/pages/item-view.dart index 23a2d1e..1e32957 100644 --- a/lib/pages/item-view.dart +++ b/lib/pages/item-view.dart @@ -1,5 +1,11 @@ +import 'dart:io'; +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_swiper/flutter_swiper.dart'; +import 'package:audioplayers/audioplayers.dart'; +import 'package:video_player/video_player.dart'; +import 'package:video_launcher/video_launcher.dart'; import '../utils/ui.dart'; import '../models/CaptureModel.dart'; @@ -20,9 +26,17 @@ class _ItemViewRouteRoute extends State { int index; String tag; SwiperController controller = new SwiperController(); + AudioPlayer audioPlayer = new AudioPlayer(); + VideoPlayerController videoController; _ItemViewRouteRoute({this.tag, this.list, this.index}); + @override + void dispose() { + super.dispose(); + videoController?.dispose(); + } + @override Widget build(BuildContext context) { return Hero( @@ -31,6 +45,13 @@ class _ItemViewRouteRoute extends State { body: Container( child: Swiper( onIndexChanged: (current) { + audioPlayer?.stop(); + if (videoController != null) { + if (videoController.value.isPlaying) { + videoController?.seekTo(Duration(seconds: 0)); + videoController?.pause(); + } + } setState(() { tag = list[current].uuid; index = current; @@ -38,7 +59,8 @@ class _ItemViewRouteRoute extends State { }, controller: controller, pagination: new SwiperPagination( - margin: new EdgeInsets.all(5.0), + alignment: Alignment.topCenter, + margin: new EdgeInsets.all(35.0), ), control: new SwiperControl( color: whiteColor, @@ -46,17 +68,12 @@ class _ItemViewRouteRoute extends State { index: index, itemBuilder: (BuildContext context, int index) { CaptureMode type = list[index].captureMode; - if (type == CaptureMode.video) return Container(); - if (type == CaptureMode.audio) return Container(); + if (type == CaptureMode.video) + return VideoPlayerWidget(videoController, list[index].filePath); + if (type == CaptureMode.audio) + return AudioPlayerWidget(audioPlayer, list[index].filePath); if (type == CaptureMode.picture) - return Container( - decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), - image: DecorationImage( - image: AssetImage(list[index].filePath), - fit: BoxFit.cover, - )), - ); + return ImageViewWidget(list[index].filePath); }, itemCount: list.length, ), @@ -70,3 +87,278 @@ class _ItemViewRouteRoute extends State { super.initState(); } } + +class ImageViewWidget extends StatelessWidget { + final String filePath; + ImageViewWidget(this.filePath); + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + new Expanded( + child: new Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + image: DecorationImage( + image: AssetImage(filePath), + fit: BoxFit.cover, + ), + ), + ), + ), + new Container( + color: primaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlatButton( + child: Text("Image Viewer", style: TextStyle(color: whiteColor)), + onPressed: null, + ), + ], + ), + ), + ], + ), + ); + } +} + +class VideoPlayerWidget extends StatefulWidget { + final String filePath; + final VideoPlayerController controller; + VideoPlayerWidget(this.controller, this.filePath); + + @override + _VideoPlayerWidget createState() => + new _VideoPlayerWidget(this.controller, this.filePath); +} + +class _VideoPlayerWidget extends State { + VideoPlayerController controller; + final String filePath; + bool _isPlaying = false; + _VideoPlayerWidget(this.controller, this.filePath); + + @override + void initState() { + super.initState(); + controller = VideoPlayerController.file(File(filePath)) + ..addListener(() { + final bool isPlaying = controller.value.isPlaying; + if (isPlaying != _isPlaying) { + if (mounted) + setState(() { + _isPlaying = isPlaying; + }); + } + }) + ..initialize().then((_) { + if (mounted) setState(() {}); + }); + } + + Future _launchVideo(path) async { + if (await canLaunchVideo(path, isLocal: true)) { + await launchVideo(path, isLocal: true); + return true; + } + return false; + } + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + new Expanded( + child: controller.value.initialized + ? Center( + child: Align( + child: VideoPlayer(controller), + ), + ) + : Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + image: DecorationImage( + image: AssetImage("assets/purple-materials.jpg"), + fit: BoxFit.cover, + ), + ), + ), + ), + new Container( + color: primaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlatButton( + child: Icon( + controller.value.isPlaying + ? Icons.pause + : Icons.play_arrow, + color: whiteColor), + onPressed: () { + if (controller.value.isPlaying) + controller.pause(); + else + controller.play(); + }, + ), + FlatButton( + child: Icon(Icons.stop, color: whiteColor), + onPressed: () { + if (controller.value.isPlaying) { + controller.seekTo(Duration(seconds: 0)); + controller.pause(); + } + }, + ), + FlatButton( + child: Icon(Icons.open_with, color: whiteColor), + onPressed: () { + _launchVideo(filePath).then((result) { + print("Error on launch video !"); + }); + }, + ), + ], + ), + ), + ], + ), + ); + } +} + +class AudioPlayerWidget extends StatefulWidget { + final String filePath; + final AudioPlayer player; + AudioPlayerWidget(this.player, this.filePath); + + @override + _AudioPlayerWidget createState() => + new _AudioPlayerWidget(this.player, this.filePath); +} + +class _AudioPlayerWidget extends State { + final String filePath; + _AudioPlayerWidget(this.audioPlayer, this.filePath); + int state = 0; + AudioPlayer audioPlayer; + Duration duration = Duration(seconds: 0), position = Duration(seconds: 0); + + Function _playHandler() { + if (state == 0) { + return () { + audioPlayer.play(filePath, isLocal: true).then((result) { + setState(() { + state = 1; + }); + }); + }; + } + if (state == 2) { + return () { + audioPlayer.resume().then((result) { + setState(() { + state = 1; + }); + }); + }; + } + return null; + } + + Function _stopHandler() { + if (state != 0) { + return () { + audioPlayer.stop().then((result) { + setState(() { + state = 0; + duration = Duration(seconds: 0); + position = Duration(seconds: 0); + }); + }); + }; + } + return null; + } + + Function _pauseHandler() { + if (state == 1) { + return () { + audioPlayer.pause().then((result) { + setState(() { + state = 2; + }); + }); + }; + } + return null; + } + + @override + void initState() { + super.initState(); + audioPlayer.positionHandler = (Duration p) { + setState(() => position = p); + }; + audioPlayer.durationHandler = (Duration d) { + setState(() => duration = d); + }; + audioPlayer.completionHandler = () { + setState(() { + state = 0; + position = duration; + }); + }; + } + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + children: [ + new Expanded( + child: new Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.5), + image: DecorationImage( + image: AssetImage("assets/purple-materials.jpg"), + fit: BoxFit.cover, + ), + ), + ), + ), + new LinearProgressIndicator( + value: (position.inSeconds / (duration.inSeconds * 1.0)), + ), + new Container( + color: primaryColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlatButton( + child: Icon(Icons.play_arrow, color: whiteColor), + onPressed: _playHandler(), + ), + FlatButton( + child: Icon(Icons.pause, color: whiteColor), + onPressed: _pauseHandler(), + ), + FlatButton( + child: Icon(Icons.stop, color: whiteColor), + onPressed: _stopHandler(), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/selected-series.dart b/lib/pages/selected-series.dart index 28e681f..6486f89 100644 --- a/lib/pages/selected-series.dart +++ b/lib/pages/selected-series.dart @@ -153,6 +153,11 @@ class _SelectedSeriesRoute extends State subtitle: Text(_makeTitle(_selectedSeries.description)), leading: Icon(Icons.description), ), + ListTile( + title: Text("UUID:"), + subtitle: Text(_selectedSeries.uuid), + leading: Icon(Icons.code), + ), ListTile( title: Text("Created at:"), subtitle: Text(formatter.format(_selectedSeries.createdAt)), @@ -231,6 +236,17 @@ class _SelectedSeriesRoute extends State title: Text( _makeTitle(_selectedSeries.title), ), + // actions: [ + // FlatButton( + // child: Text( + // "Open as", + // style: TextStyle( + // color: whiteColor, + // ), + // ), + // onPressed: _openIntent, + // ) + // ], ), body: _items.length > 0 ? _tabBarView() : _listView(), bottomNavigationBar: _items.length > 0 ? _bottomBar() : null, diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index df75799..260b296 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,8 +1,13 @@ +import 'dart:io'; +import 'package:archive/archive_io.dart'; +import 'dart:async'; import 'package:flutter/material.dart'; import '../utils/ui.dart'; import '../config/application.dart'; +String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); + class SettingsRoute extends StatefulWidget { @override _SettingsRoute createState() => new _SettingsRoute(); @@ -34,26 +39,46 @@ class _SettingsRoute extends State { ? _showLoading() : ListView( children: [ - // ListTile( - // title: Text("Clear database"), - // leading: Icon(Icons.storage), - // onTap: () { - // confirmDialog(context, "Are you sure ?", () {}); - // }, - // ), + ListTile( + title: Text("Backup from contents"), + leading: Icon(Icons.archive), + onTap: () { + confirmDialog(context, + "This may take long, do you want to continue ?", () { + setState(() => _loading = true); + Future.delayed(Duration(seconds: 1)).then((_) { + var encoder = new ZipFileEncoder(); + encoder.create( + '${Application.mainDir}/CantonFair_${timestamp()}.zip'); + encoder.addDirectory(new Directory(Application.appDir)); + encoder.close(); + setState(() => _loading = false); + }); + }); + }, + ), + Divider(), + ListTile( + title: Text("Clear database"), + leading: Icon(Icons.storage), + onTap: () { + confirmDialog(context, "Are you sure ?", () { + setState(() => _loading = true); + Application.forceDelete().then((_) { + Navigator.pushReplacementNamed(context, "/"); + }); + }); + }, + ), ListTile( title: Text("Backup from database"), leading: Icon(Icons.backup), onTap: () { confirmDialog( context, "Do you want to backup your database ?", () { - setState(() { - _loading = true; - }); + setState(() => _loading = true); Application.backupDatabase().then((_) { - setState(() { - _loading = false; - }); + setState(() => _loading = false); }); }); }, diff --git a/lib/pages/splash-screen.dart b/lib/pages/splash-screen.dart index aea75f5..8b2986a 100644 --- a/lib/pages/splash-screen.dart +++ b/lib/pages/splash-screen.dart @@ -72,11 +72,16 @@ class _SplashScreenRoute extends State { Future requestPermissions() async { final writeAccess = await SimplePermissions.requestPermission( Permission.WriteExternalStorage); + final readAccess = await SimplePermissions.requestPermission( + Permission.ReadExternalStorage); final cameraAccess = await SimplePermissions.requestPermission(Permission.Camera); final recordAudio = await SimplePermissions.requestPermission(Permission.RecordAudio); + if (readAccess != PermissionStatus.authorized) { + return false; + } if (writeAccess != PermissionStatus.authorized) { return false; } @@ -93,7 +98,6 @@ class _SplashScreenRoute extends State { @override void initState() { super.initState(); - requestPermissions().then((ok) { if (ok) { _handleStart(); @@ -104,7 +108,7 @@ class _SplashScreenRoute extends State { } _handleStart() { - Application.cache = new Map(); + Application.cache = new Map(); try { availableCameras().then((cameras) { @@ -119,10 +123,12 @@ class _SplashScreenRoute extends State { ); } - Application.closeDatabase().then((result) { - Application.initDatabase().then((data) { - Category.getCategories().then((categories) { - Application.cache["categories"] = categories; + Application.closeDatabase().then((_) { + Application.initDatabase().then((_) { + Future.wait([ + Category.getCategories(), + ]).then((results) { + Application.cache["categories"] = results[0]; _startApp(); }); }); diff --git a/lib/utils/card.dart b/lib/utils/card.dart index dde40e0..4f82a59 100644 --- a/lib/utils/card.dart +++ b/lib/utils/card.dart @@ -1,6 +1,4 @@ -import 'dart:io'; import 'package:flutter/painting.dart'; -import 'package:video_player/video_player.dart'; import 'package:flutter/material.dart'; import '../models/CaptureModel.dart'; @@ -123,36 +121,13 @@ class _VideoAppCardState extends State { this.loadingImage = loadingImage; } - void dispose() { - print("video dispose !"); - super.dispose(); - } - - VideoPlayerController _controller; - bool _isPlaying = false; - @override - void initState() { - super.initState(); - _controller = VideoPlayerController.file( - File(filepath), - ) - ..addListener(() { - final bool isPlaying = _controller.value.isPlaying; - if (isPlaying != _isPlaying) { - setState(() { - _isPlaying = isPlaying; - }); - } - }) - ..initialize().then((_) { - setState(() {}); - }); - } - - Widget _loading() { + Widget build(BuildContext context) { + print("Building video card ..."); return Container( + child: _overlay(context, CaptureMode.video), decoration: new BoxDecoration( + color: Colors.black.withOpacity(0.5), image: new DecorationImage( image: new AssetImage(loadingImage), fit: BoxFit.cover, @@ -160,20 +135,4 @@ class _VideoAppCardState extends State { ), ); } - - Widget _player() { - // FUCCKK THISS SHITT ! - // flutter has long way to become a perfect mobile framework - // cause of texture does not support border radius - return Container( - color: Colors.black.withOpacity(0.5), - child: VideoPlayer(_controller), - ); - } - - @override - Widget build(BuildContext context) { - print("Building video card ..."); - return _controller.value.initialized ? _player() : _loading(); - } } diff --git a/packages/flutter_video_launcher/.gitignore b/packages/flutter_video_launcher/.gitignore new file mode 100644 index 0000000..14c7d4c --- /dev/null +++ b/packages/flutter_video_launcher/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.atom/ +.idea +.packages +.pub/ +build/ +ios/.generated/ +packages +pubspec.lock diff --git a/packages/flutter_video_launcher/CHANGELOG.md b/packages/flutter_video_launcher/CHANGELOG.md new file mode 100644 index 0000000..2aeee0f --- /dev/null +++ b/packages/flutter_video_launcher/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +## 0.3.0 + +- fix the local video playing on android +- fix the example + +## 0.2.2 + +- changed Flutter dependency in pubspec.yaml to an SDK dependency + +## 0.2.1 + +- fix the iOS local playing +- add download/play local in the example + +## 0.2.0 + +- android + +## 0.1.0 + +- iOS \ No newline at end of file diff --git a/packages/flutter_video_launcher/LICENSE b/packages/flutter_video_launcher/LICENSE new file mode 100644 index 0000000..86928f6 --- /dev/null +++ b/packages/flutter_video_launcher/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 Your Company. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Your Company nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/flutter_video_launcher/README.md b/packages/flutter_video_launcher/README.md new file mode 100644 index 0000000..0f687b9 --- /dev/null +++ b/packages/flutter_video_launcher/README.md @@ -0,0 +1,96 @@ +# VideoLauncher + +## Example + +```bash +cd flutter_video_launcher/example +flutter packages get +flutter run +``` + +![screenshot](https://github.com/rxlabz/flutter_video_launcher/blob/master/screenshot.png) + +## Usage + +To use this plugin, add video_launcher as a dependency in your pubspec.yaml file. + +After importing 'package:video_launcher/video_launcher.dart' the directories can be queried as follows + +## Remote video + +To launch a video : + +```dart +Future _launch(String url) async { + if (await canLaunchVideo(url)) { + await launchVideo(url); + } else { + throw 'Could not launch $url'; + } + } +``` + +## Local video + +To play a local video file : + +```dart +Future _launch(String url) async { + if (await canLaunchVideo(url, isLocal:true)) { + await launchVideo(url, isLocal:true); + } else { + throw 'Could not launch $url'; + } + } +``` + +## Embedded asset + +To play video embedded assets, you can copy them to the app storage folder, and the play them "locally" + +```dart +void _loadOrLaunchLocalAsset() => + localAssetPath != null ? _launchLocalAsset() : copyLocalAsset(); + +Future copyLocalAsset() async { + final dir = await getApplicationDocumentsDirectory(); + final file = new File("${dir.path}/video_asset.mp4"); + final videoData = await rootBundle.load("assets/video.mp4"); + final bytes = videoData.buffer.asUint8List(); + file.writeAsBytes(bytes, flush: true); + setState(() { + localAssetPath = file.path; + _launchLocalAsset(); +}); +} + +void _launchLocalAsset() => + setState(() => _launched = _launchVideo(localAssetPath, isLocal: true)); +``` + +Please see the example app of this plugin for a full example. + + +## :warning: iOS App Transport Security + +By default iOS forbids loading from non-https url. To cancel this restriction edit your .plist and add : + +```xml +NSAppTransportSecurity + + NSAllowsArbitraryLoads + + +``` + +cf. [Configuring App Transport Security Exceptions in iOS](https://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/) + +## Troubleshooting + +If you get a MissingPluginException, try to `flutter build apk` on Android, or `flutter build ios` + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). + \ No newline at end of file diff --git a/packages/flutter_video_launcher/android/.gitignore b/packages/flutter_video_launcher/android/.gitignore new file mode 100644 index 0000000..5c4ef82 --- /dev/null +++ b/packages/flutter_video_launcher/android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures + +/gradle +/gradlew +/gradlew.bat diff --git a/packages/flutter_video_launcher/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/packages/flutter_video_launcher/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..00c52dd --- /dev/null +++ b/packages/flutter_video_launcher/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,25 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; +import bz.rxla.video_launcher.VideoLauncherPlugin; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + VideoLauncherPlugin.registerWith(registry.registrarFor("bz.rxla.video_launcher.VideoLauncherPlugin")); + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/packages/flutter_video_launcher/android/build.gradle b/packages/flutter_video_launcher/android/build.gradle new file mode 100644 index 0000000..f39be84 --- /dev/null +++ b/packages/flutter_video_launcher/android/build.gradle @@ -0,0 +1,32 @@ +group 'bz.rxla.video_launcher' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:2.3.0' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 25 + buildToolsVersion '25.0.3' + + defaultConfig { + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} diff --git a/packages/flutter_video_launcher/android/gradle.properties b/packages/flutter_video_launcher/android/gradle.properties new file mode 100644 index 0000000..8bd86f6 --- /dev/null +++ b/packages/flutter_video_launcher/android/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536M diff --git a/packages/flutter_video_launcher/android/settings.gradle b/packages/flutter_video_launcher/android/settings.gradle new file mode 100644 index 0000000..1305243 --- /dev/null +++ b/packages/flutter_video_launcher/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'video_launcher' diff --git a/packages/flutter_video_launcher/android/src/main/AndroidManifest.xml b/packages/flutter_video_launcher/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..02d94d5 --- /dev/null +++ b/packages/flutter_video_launcher/android/src/main/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/flutter_video_launcher/android/src/main/java/bz/rxla/video_launcher/VideoLauncherPlugin.java b/packages/flutter_video_launcher/android/src/main/java/bz/rxla/video_launcher/VideoLauncherPlugin.java new file mode 100644 index 0000000..3fdf6ba --- /dev/null +++ b/packages/flutter_video_launcher/android/src/main/java/bz/rxla/video_launcher/VideoLauncherPlugin.java @@ -0,0 +1,85 @@ +package bz.rxla.video_launcher; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry; + +import java.io.File; +import java.util.HashMap; + + +/** + * VideoLauncherPlugin + */ +public class VideoLauncherPlugin implements MethodChannel.MethodCallHandler { + private final Activity activity; + + public static void registerWith(PluginRegistry.Registrar registrar) { + MethodChannel channel = + new MethodChannel(registrar.messenger(), "bz.rxla.flutter/video_launcher"); + VideoLauncherPlugin instance = new VideoLauncherPlugin(registrar.activity()); + channel.setMethodCallHandler(instance); + } + + private VideoLauncherPlugin(Activity activity) { + this.activity = activity; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + String url = ((HashMap) call.arguments()).get("url").toString(); + Boolean isLocal = ((HashMap) call.arguments()).get("isLocal").toString() == "1"; + if (call.method.equals("canLaunchVideo")) { + canLaunchVideo(url, result, isLocal); + } else if (call.method.equals("launchVideo")) { + launchURLVideo(url, result, isLocal); + } else { + result.notImplemented(); + } + } + + private void launchURLVideo(String url, MethodChannel.Result result, Boolean isLocal) { + if( isLocal ){ + File f = new File(url); + f.setReadable(true, false); + Uri videoUri = Uri.fromFile(f); + Intent launchIntent = new Intent(Intent.ACTION_VIEW); + launchIntent.setDataAndType(Uri.parse(videoUri.toString()), "video/mp4"); + activity.startActivity(launchIntent); + result.success(null); + } else { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setDataAndType(Uri.parse(url), "video/mp4"); + activity.startActivity(intent); + result.success(null); + } + } + + private void canLaunchVideo(String url, MethodChannel.Result result, Boolean isLocal) { + boolean canLaunch; + + if (isLocal) { + File f = new File(url); + f.setReadable(true, false); + Uri videoUri = Uri.fromFile(f); + Intent launchIntent = new Intent(Intent.ACTION_VIEW); + launchIntent.setDataAndType(Uri.parse(videoUri.toString()), "video/mp4"); + ComponentName componentName = launchIntent.resolveActivity(activity.getPackageManager()); + canLaunch = componentName != null + && !"{com.android.fallback/com.android.fallback.Fallback}" + .equals(componentName.toShortString()); + } else { + Intent launchIntent = new Intent(Intent.ACTION_VIEW); + launchIntent.setDataAndType(Uri.parse(url), "video/mp4"); + ComponentName componentName = launchIntent.resolveActivity(activity.getPackageManager()); + canLaunch = componentName != null + && !"{com.android.fallback/com.android.fallback.Fallback}" + .equals(componentName.toShortString()); + } + result.success(canLaunch); + } +} diff --git a/packages/flutter_video_launcher/ios/.gitignore b/packages/flutter_video_launcher/ios/.gitignore new file mode 100644 index 0000000..956c87f --- /dev/null +++ b/packages/flutter_video_launcher/ios/.gitignore @@ -0,0 +1,31 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + diff --git a/packages/flutter_video_launcher/ios/Assets/.gitkeep b/packages/flutter_video_launcher/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.h b/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.h new file mode 100644 index 0000000..a25746c --- /dev/null +++ b/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface VideoLauncherPlugin : NSObject +@end diff --git a/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.m b/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.m new file mode 100644 index 0000000..ad1e59a --- /dev/null +++ b/packages/flutter_video_launcher/ios/Classes/VideoLauncherPlugin.m @@ -0,0 +1,74 @@ +#import "VideoLauncherPlugin.h" +#import +#import + +@implementation VideoLauncherPlugin + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:@"bz.rxla.flutter/video_launcher" + binaryMessenger:registrar.messenger]; + [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) { + NSString *url = call.arguments[@"url"]; + BOOL isLocal = call.arguments[@"isLocal"] == @1; + + if ([@"canLaunchVideo" isEqualToString:call.method]) { + result(isLocal ? @([self fileExists:url]) : @([self canLaunchURLVideo:url])); + } else if ([@"launchVideo" isEqualToString:call.method]) { + [self launchURLVideo:url result:result isLocal:isLocal]; + } else { + result(FlutterMethodNotImplemented); + } + }]; +} + ++ (BOOL)fileExists:(NSString *)pathString { + NSFileManager *fileManager = [NSFileManager defaultManager]; + return [fileManager fileExistsAtPath:pathString]; +} + ++ (BOOL)canLaunchURLVideo:(NSString *)urlString { + NSURL *url = [NSURL URLWithString:urlString]; + UIApplication *application = [UIApplication sharedApplication]; + return [application canOpenURL:url]; +} + ++ (void)launchURLVideo:(NSString *)urlString result:(FlutterResult)result isLocal:(BOOL)isLocal { + NSURL *url = isLocal ? [NSURL fileURLWithPath:urlString] : [NSURL URLWithString:urlString]; + + AVPlayer *player = [AVPlayer playerWithURL:url]; + + AVPlayerViewController *playerViewController = [AVPlayerViewController new]; + playerViewController.showsPlaybackControls = YES; + playerViewController.player = player; + + UIViewController *viewController = [UIApplication sharedApplication].keyWindow.rootViewController; + [viewController.view addSubview:playerViewController.view]; + [viewController presentViewController:playerViewController animated:YES completion:nil]; + + [player play]; + +// Using ifdef as workaround to support running with Xcode 7.0 and sdk version 9 +// where the dynamic check fails. +#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_9_0 + [application openURL:url + options:@{} + completionHandler:^(BOOL success) { + [self sendResult:success result:result url:url]; + }]; +#else + [self sendResult:YES result:result url:url]; +#endif +} + ++ (void)sendResult:(BOOL)success result:(FlutterResult)result url:(NSURL *)url { + if (success) { + result(nil); + } else { + result([FlutterError errorWithCode:@"Error" + message:[NSString stringWithFormat:@"Error while launching %@", url] + details:nil]); + } +} + +@end diff --git a/packages/flutter_video_launcher/ios/Flutter/Generated.xcconfig b/packages/flutter_video_launcher/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000..f5aa7a3 --- /dev/null +++ b/packages/flutter_video_launcher/ios/Flutter/Generated.xcconfig @@ -0,0 +1,9 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/home/aiden/flutter/flutter +FLUTTER_APPLICATION_PATH=/home/aiden/flutter/projects/cantonfair/packages/flutter_video_launcher +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_MODE=debug +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/home/aiden/flutter/flutter/bin/cache/artifacts/engine/ios +FLUTTER_BUILD_NAME=0.3.0 diff --git a/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.h b/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 0000000..3b700eb --- /dev/null +++ b/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +#endif /* GeneratedPluginRegistrant_h */ diff --git a/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.m b/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 0000000..9fde087 --- /dev/null +++ b/packages/flutter_video_launcher/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +#import "GeneratedPluginRegistrant.h" +#import + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { + [VideoLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"VideoLauncherPlugin"]]; +} + +@end diff --git a/packages/flutter_video_launcher/ios/video_launcher.podspec b/packages/flutter_video_launcher/ios/video_launcher.podspec new file mode 100644 index 0000000..6e11ba7 --- /dev/null +++ b/packages/flutter_video_launcher/ios/video_launcher.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'video_launcher' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end + diff --git a/packages/flutter_video_launcher/lib/video_launcher.dart b/packages/flutter_video_launcher/lib/video_launcher.dart new file mode 100644 index 0000000..80870f3 --- /dev/null +++ b/packages/flutter_video_launcher/lib/video_launcher.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +const _channel = const MethodChannel('bz.rxla.flutter/video_launcher'); + +/// Parses the specified video URL string and delegates handling of it to the +/// underlying platform. +/// +/// The returned future completes with a [PlatformException] on invalid URLs and +/// schemes which cannot be handled, that is when [canLaunchVideo] would complete +/// with false. +Future launchVideo(String urlString, {bool isLocal:false}) { + return _channel.invokeMethod( + /* FIXME had some trouble to send a false BOOL to objC => for now I send 1 || 0 */ + 'launchVideo',{"url":urlString, "isLocal":isLocal ? 1 : 0 }, + ); +} + +/// Checks whether the specified video URL can be handled by some app installed on the +/// device. +Future canLaunchVideo(String urlString, {bool isLocal:false}) async { + if (urlString == null) return false; + return await _channel.invokeMethod( + 'canLaunchVideo',{"url":urlString, "isLocal":isLocal ? 1 : 0} + ); +} diff --git a/packages/flutter_video_launcher/pubspec.yaml b/packages/flutter_video_launcher/pubspec.yaml new file mode 100644 index 0000000..d8cdd3b --- /dev/null +++ b/packages/flutter_video_launcher/pubspec.yaml @@ -0,0 +1,17 @@ +name: video_launcher +description: Fullscreen video launcher plugin +version: 0.3.0 +author: Erick Ghaumez +homepage: https://github.com/rxlabz/flutter_video_launcher + +flutter: + plugin: + androidPackage: bz.rxla.video_launcher + pluginClass: VideoLauncherPlugin + +dependencies: + flutter: + sdk: flutter + +environment: + sdk: ">=2.0.0 <2.2.0" diff --git a/packages/flutter_video_launcher/screenshot.png b/packages/flutter_video_launcher/screenshot.png new file mode 100755 index 0000000..ab41470 Binary files /dev/null and b/packages/flutter_video_launcher/screenshot.png differ diff --git a/packages/flutter_video_launcher/video_launcher.iml b/packages/flutter_video_launcher/video_launcher.iml new file mode 100644 index 0000000..7f9681f --- /dev/null +++ b/packages/flutter_video_launcher/video_launcher.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 9bde5d5..213f31a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -8,8 +8,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.32.4" + android_intent: + dependency: "direct main" + description: + name: android_intent + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" archive: - dependency: transitive + dependency: "direct main" description: name: archive url: "https://pub.dartlang.org" @@ -36,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.6" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.8" boolean_selector: dependency: transitive description: @@ -186,6 +200,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.4" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.10" intl: dependency: "direct main" description: @@ -298,6 +319,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" plugin: dependency: transitive description: @@ -471,6 +499,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + video_launcher: + dependency: "direct main" + description: + path: "packages/flutter_video_launcher" + relative: true + source: path + version: "0.3.0" video_player: dependency: "direct main" description: @@ -514,5 +549,5 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.0.0 <3.0.0" + dart: ">=2.0.0 <2.2.0" flutter: ">=0.2.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8413f5e..752eeaa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: cupertino_icons: ^0.1.2 camera: 0.2.4 + android_intent: 0.2.1 path_provider: ^0.4.1 sqflite: any fluro: "^1.3.4" @@ -29,6 +30,11 @@ dependencies: image: any video_player: "0.6.5" flutter_swiper: any + audioplayers: 0.7.8 + archive: 2.0.4 + image_picker: 0.4.10 + video_launcher: + path: './packages/flutter_video_launcher' dev_dependencies: flutter_test: