From 5fc77b4abb8bc482ecc02931c663e0be7890434e Mon Sep 17 00:00:00 2001 From: lukas-heiligenbrunner Date: Sun, 28 Aug 2022 22:51:12 +0200 Subject: [PATCH] linux build cache previews in sqlite db actor page outsouce different players in seperate classes --- .gitlab-ci.yml | 13 + lib/DrawerPage.dart | 21 +- lib/db/database.dart | 55 +++++ lib/main.dart | 3 + lib/navigation/actor_screen.dart | 65 +++++ lib/navigation/categorie_screen.dart | 39 +-- lib/navigation/settings_screen.dart | 47 ++++ lib/navigation/shufflescreen.dart | 5 +- lib/navigation/video_feed.dart | 6 +- lib/preview/actor_feed.dart | 37 +++ lib/{video_screen => preview}/actor_tile.dart | 24 +- lib/preview/preview_grid.dart | 73 +++--- lib/preview/preview_tile.dart | 158 +++++++------ lib/preview/tag_tile.dart | 32 +++ lib/types/video.dart | 12 + .../{actor_view.dart => info_view.dart} | 13 +- lib/video_screen/videoscreen.dart | 165 +++++++++++++ lib/video_screen/videoscreen_desktop.dart | 223 +++--------------- lib/video_screen/videoscreen_mobile.dart | 59 +++++ lib/video_screen/videoscreen_web.dart | 52 ---- pubspec.lock | 39 ++- pubspec.yaml | 3 + 22 files changed, 752 insertions(+), 392 deletions(-) create mode 100644 lib/db/database.dart create mode 100644 lib/navigation/actor_screen.dart create mode 100644 lib/navigation/settings_screen.dart create mode 100644 lib/preview/actor_feed.dart rename lib/{video_screen => preview}/actor_tile.dart (67%) create mode 100644 lib/preview/tag_tile.dart create mode 100644 lib/types/video.dart rename lib/video_screen/{actor_view.dart => info_view.dart} (84%) create mode 100644 lib/video_screen/videoscreen.dart create mode 100644 lib/video_screen/videoscreen_mobile.dart delete mode 100644 lib/video_screen/videoscreen_web.dart diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9254a57..7ec8446 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,6 +14,19 @@ flutter_build_android: #Job name paths: - build/app/outputs/apk/release/app-release.apk +linux_build: + stage: build + script: + - apt-get update + - apt-get install -y --no-install-recommends cmake ninja-build clang build-essential pkg-config libgtk-3-dev liblzma-dev lcov + - flutter config --enable-linux-desktop + - flutter packages get + - flutter build linux + artifacts: + paths: + - build/linux/x64/release/bundle/* + + flutter_lint: stage: build script: diff --git a/lib/DrawerPage.dart b/lib/DrawerPage.dart index c69b047..9496370 100644 --- a/lib/DrawerPage.dart +++ b/lib/DrawerPage.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:openmediacentermobile/navigation/settings_screen.dart'; +import 'navigation/actor_screen.dart'; import 'navigation/categorie_screen.dart'; import 'navigation/shufflescreen.dart'; import 'navigation/video_feed.dart'; @@ -15,7 +17,7 @@ class DrawerPage extends StatefulWidget { _DrawerPageState createState() => _DrawerPageState(); } -enum Section { HOME, SHUFFLE, LOGOUT, CATEGORIE } +enum Section { HOME, SHUFFLE, LOGOUT, CATEGORIE, ACTOR } class _DrawerPageState extends State { Section _sec = Section.HOME; @@ -37,7 +39,7 @@ class _DrawerPageState extends State { break; case Section.LOGOUT: - body = const Text("also todo"); + body = SettingsScreen(); title = "Settings"; break; @@ -45,6 +47,11 @@ class _DrawerPageState extends State { body = CategorieScreen(); title = "Categories"; break; + + case Section.ACTOR: + body = ActorScreen(); + title = "Actors"; + break; } final loginCtx = LoginContext.of(context); @@ -94,6 +101,16 @@ class _DrawerPageState extends State { Navigator.pop(context); }, ), + ListTile( + title: const Text('Actors'), + leading: const Icon(Icons.people), + onTap: () { + setState(() { + _sec = Section.ACTOR; + }); + Navigator.pop(context); + }, + ), ListTile( title: const Text('Settings'), leading: const Icon(Icons.settings), diff --git a/lib/db/database.dart b/lib/db/database.dart new file mode 100644 index 0000000..48d31b5 --- /dev/null +++ b/lib/db/database.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart'; +import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; + +import '../log/log.dart'; + +class Db { + late Database _db; + + void init() async { + if (kIsWeb) { + Log.i("Database on web is not supported"); + return; + } + String dbpath = 'previews.db'; + if (defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS) { + dbpath = join(await getDatabasesPath(), dbpath); + } else { + // Initialize FFI + sqfliteFfiInit(); + // Change the default factory + databaseFactory = databaseFactoryFfi; + } + + _db = await openDatabase( + // Set the path to the database. Note: Using the `join` function from the + // `path` package is best practice to ensure the path is correctly + // constructed for each platform. + join(await getDatabasesPath(), 'previews.db'), + onCreate: (db, version) { + // Run the CREATE TABLE statement on the database. + return db.execute( + 'CREATE TABLE previews(id INTEGER PRIMARY KEY, thumbnail BLOB)', + ); + }, + // Set the version. This executes the onCreate function and provides a + // path to perform database upgrades and downgrades. + version: 1, + ); + } + + Database db() { + return _db; + } + + static final Db _singleton = Db._internal(); + + factory Db() { + return _singleton; + } + + Db._internal(); +} diff --git a/lib/main.dart b/lib/main.dart index 80b7c05..77dddf0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:openmediacentermobile/app.dart'; +import 'db/database.dart'; import 'log/log.dart'; import 'login/logincontext.dart'; import 'platform.dart'; @@ -15,6 +16,8 @@ void main() async { await loadDeviceInfo(); } + Db().init(); + // RawKeyboard.instance.addListener((event) { // if (LogicalKeyboardKey.arrowLeft == event.logicalKey) { // FocusManager.instance.primaryFocus?.focusInDirection(TraversalDirection.left); diff --git a/lib/navigation/actor_screen.dart b/lib/navigation/actor_screen.dart new file mode 100644 index 0000000..35c1549 --- /dev/null +++ b/lib/navigation/actor_screen.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:openmediacentermobile/types/actor.dart'; +import 'package:openmediacentermobile/preview/actor_tile.dart'; + +import '../api/api.dart'; +import '../screen_loading.dart'; + +class ActorScreen extends StatefulWidget { + const ActorScreen({Key? key}) : super(key: key); + + @override + State createState() => _ActorScreenState(); +} + +class _ActorScreenState extends State { + late Future> _categories; + + Future> loadVideoData() async { + final data = await API.query("actor", "getAllActors", {}); + + final d = (jsonDecode(data) ?? []) as List; + final actors = d.map((e) => Actor.fromJson(e)).toList(growable: false); + return actors; + } + + @override + void initState() { + super.initState(); + _categories = loadVideoData(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _categories, + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return ScreenLoading(); + } + + if (snapshot.hasError) { + return Text("Error"); + } else if (snapshot.hasData) { + return Padding( + padding: EdgeInsets.all(5), + child: SingleChildScrollView( + child: Wrap( + spacing: 5, + runSpacing: 5, + alignment: WrapAlignment.start, + children: snapshot.data! + .map((e) => ActorTile(actor: e)) + .toList(growable: false), + ), + ), + ); + } else { + return ScreenLoading(); + } + }, + ); + } +} diff --git a/lib/navigation/categorie_screen.dart b/lib/navigation/categorie_screen.dart index c1aa269..342ac3a 100644 --- a/lib/navigation/categorie_screen.dart +++ b/lib/navigation/categorie_screen.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import '../navigation/video_feed.dart'; +import 'package:openmediacentermobile/preview/tag_tile.dart'; import '../screen_loading.dart'; import '../api/api.dart'; @@ -45,32 +45,17 @@ class _CategorieScreenState extends State { } else if (snapshot.hasData) { return Padding( padding: EdgeInsets.all(5), - child: Wrap( - spacing: 5, - runSpacing: 5, - alignment: WrapAlignment.start, - children: snapshot.data! - .map((e) => ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => Scaffold( - appBar: AppBar(title: Text(e.tagName)), - body: VideoFeed(tag: e), - ), - ), - ); - }, - style: ElevatedButton.styleFrom( - primary: Color(0x6a94a6ff)), - child: SizedBox( - child: Center(child: Text(e.tagName)), - height: 100, - width: 100, - ), - )) - .toList(growable: false), + child: SingleChildScrollView( + child: Wrap( + spacing: 5, + runSpacing: 5, + alignment: WrapAlignment.start, + children: snapshot.data! + .map((e) => TagTile( + tag: e, + )) + .toList(growable: false), + ), ), ); } else { diff --git a/lib/navigation/settings_screen.dart b/lib/navigation/settings_screen.dart new file mode 100644 index 0000000..9d72a8d --- /dev/null +++ b/lib/navigation/settings_screen.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +import '../db/database.dart'; + +class SettingsScreen extends StatefulWidget { + const SettingsScreen({Key? key}) : super(key: key); + + @override + State createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + int dbsize = 0; + + @override + void initState() { + super.initState(); + loadDBSize(); + } + + void loadDBSize() async { + final int cnt = (await Db().db().rawQuery("pragma page_count;"))[0] + ["page_count"] as int; + final int pagesize = + (await Db().db().rawQuery("pragma page_size;"))[0]["page_size"] as int; + setState(() { + dbsize = cnt * pagesize; + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ElevatedButton( + onPressed: () async { + await Db().db().delete("previews"); + // shrink the db file size + await Db().db().execute("VACUUM"); + loadDBSize(); + }, + child: const Text("Delete cache!")), + Text("db size: ${dbsize / 1024} kb") + ], + ); + } +} diff --git a/lib/navigation/shufflescreen.dart b/lib/navigation/shufflescreen.dart index 2278ffb..7d100e4 100644 --- a/lib/navigation/shufflescreen.dart +++ b/lib/navigation/shufflescreen.dart @@ -1,12 +1,11 @@ import 'dart:convert'; import 'dart:math'; - import 'package:flutter/material.dart'; -import 'package:openmediacentermobile/preview/preview_grid.dart'; -import '../preview/preview_tile.dart'; +import '../preview/preview_grid.dart'; import '../api/api.dart'; import '../platform.dart'; +import '../types/video.dart'; class ShuffleScreen extends StatefulWidget { const ShuffleScreen({Key? key}) : super(key: key); diff --git a/lib/navigation/video_feed.dart b/lib/navigation/video_feed.dart index c3486ea..4afff40 100644 --- a/lib/navigation/video_feed.dart +++ b/lib/navigation/video_feed.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:openmediacentermobile/api/api.dart'; -import 'package:openmediacentermobile/preview/preview_grid.dart'; +import '../api/api.dart'; import '../log/log.dart'; -import '../preview/preview_tile.dart'; +import '../preview/preview_grid.dart'; import '../types/tag.dart'; +import '../types/video.dart'; class VideoFeed extends StatefulWidget { const VideoFeed({Key? key, this.tag}) : super(key: key); diff --git a/lib/preview/actor_feed.dart b/lib/preview/actor_feed.dart new file mode 100644 index 0000000..8614394 --- /dev/null +++ b/lib/preview/actor_feed.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../api/api.dart'; +import 'preview_grid.dart'; +import '../types/actor.dart'; +import '../types/video.dart'; + +class ActorFeed extends StatefulWidget { + const ActorFeed({Key? key, required this.actor}) : super(key: key); + final Actor actor; + + @override + State createState() => _ActorFeedState(); +} + +class _ActorFeedState extends State { + Future> loadData() async { + final data = await API + .query("actor", "getActorInfo", {'ActorId': widget.actor.actorId}); + + final d = jsonDecode(data); + + List dta = + (d['Videos'] as List).map((e) => VideoT.fromJson(e)).toList(); + + return dta; + } + + @override + Widget build(BuildContext context) { + return PreviewGrid( + videoLoader: () => loadData(), + ); + } +} diff --git a/lib/video_screen/actor_tile.dart b/lib/preview/actor_tile.dart similarity index 67% rename from lib/video_screen/actor_tile.dart rename to lib/preview/actor_tile.dart index f77448b..5cd0b92 100644 --- a/lib/video_screen/actor_tile.dart +++ b/lib/preview/actor_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:openmediacentermobile/preview/actor_feed.dart'; import '../platform.dart'; import '../types/actor.dart'; @@ -27,9 +28,16 @@ class _ActorTileState extends State { overflow: TextOverflow.clip, maxLines: 1, ), + // todo implement case where we have really an picture SizedBox( height: 100, width: 100, + child: Center( + child: Icon( + Icons.person_outline, + size: 56, + ), + ), ) ], ), @@ -44,13 +52,15 @@ class _ActorTileState extends State { onLongPressEnd: (details) {}, child: InkWell( onTap: () { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => - // VideoScreen(metaData: widget.dta), - // ), - // ); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar(title: Text(widget.actor.name)), + body: ActorFeed(actor: widget.actor), + ), + ), + ); }, ), ), diff --git a/lib/preview/preview_grid.dart b/lib/preview/preview_grid.dart index 950fd59..87dd7b6 100644 --- a/lib/preview/preview_grid.dart +++ b/lib/preview/preview_grid.dart @@ -2,9 +2,11 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; -import 'package:openmediacentermobile/platform.dart'; -import 'package:openmediacentermobile/preview/preview_tile.dart'; -import 'package:openmediacentermobile/screen_loading.dart'; + +import '../platform.dart'; +import '../screen_loading.dart'; +import '../types/video.dart'; +import 'preview_tile.dart'; class PreviewGrid extends StatefulWidget { const PreviewGrid( @@ -70,32 +72,45 @@ class _PreviewGridState extends State { Column( children: [ if (widget.headerBuilder != null) widget.headerBuilder!(this), - Expanded( - child: MasonryGridView.count( - // every tile should be at max 330 pixels long... - crossAxisCount: isTV() ? width ~/ 200 : width ~/ 275, - // crossAxisCount: isTV() ? width ~/ 200 : width ~/ 330, - itemCount: data.length, - mainAxisSpacing: 4, - crossAxisSpacing: 4, - padding: EdgeInsets.all(5), - itemBuilder: (context, index) { - return PreviewTile( - dta: data[index], - onLongPress: (img) { - setState(() { - _previewImage = img; - }); - }, - onLongPressEnd: () { - setState(() { - _previewImage = null; - }); - }, - ); - }, - ), - ), + data.length > 0 + ? Expanded( + child: MasonryGridView.count( + // every tile should be at max 330 pixels long... + crossAxisCount: isTV() ? width ~/ 200 : width ~/ 275, + // crossAxisCount: isTV() ? width ~/ 200 : width ~/ 330, + itemCount: data.length, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + padding: EdgeInsets.all(5), + itemBuilder: (context, index) { + return PreviewTile( + dta: data[index], + onLongPress: (img) { + setState(() { + _previewImage = img; + }); + }, + onLongPressEnd: () { + setState(() { + _previewImage = null; + }); + }, + ); + }, + ), + ) + : Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + height: 32, + ), + Icon(Icons.warning_amber, size: 52), + Text("no item available") + ], + ), + ), if (widget.footerBuilder != null) widget.footerBuilder!(this), ], ), diff --git a/lib/preview/preview_tile.dart b/lib/preview/preview_tile.dart index ccb409b..74b660b 100644 --- a/lib/preview/preview_tile.dart +++ b/lib/preview/preview_tile.dart @@ -1,26 +1,16 @@ import 'dart:convert'; +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:openmediacentermobile/video_screen/videoscreen_desktop.dart' - if (dart.library.html) 'package:openmediacentermobile/video_screen/videoscreen_web.dart' - if (dart.library.io) 'package:openmediacentermobile/video_screen/videoscreen_desktop.dart'; +import 'package:sqflite/sqflite.dart'; import '../api/api.dart'; +import '../db/database.dart'; +import '../log/log.dart'; import '../platform.dart'; - -// todo put this type in sperate class! -class VideoT { - int id; - String title; - double ratio; - - VideoT(this.title, this.id, this.ratio); - - factory VideoT.fromJson(dynamic json) { - return VideoT(json['MovieName'] as String, json['MovieId'] as int, - (json['Ratio'] as num).toDouble()); - } -} +import '../types/video.dart'; +import '../video_screen/videoscreen.dart'; class PreviewTile extends StatefulWidget { const PreviewTile( @@ -42,6 +32,7 @@ class _PreviewTileState extends State { super.initState(); _preview = loadData(); + Log.d("initial load of tile"); } @override @@ -52,15 +43,45 @@ class _PreviewTileState extends State { setState(() { _preview = loadData(); }); + Log.i("load of tile due to change"); } } + Future insert(int id, Uint8List pic) async { + await Db().db().insert( + 'previews', + {'id': id, 'thumbnail': pic}, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future _fetchThumbnail(int id) async { + final base64str = + await API.query("video", "readThumbnail", {'Movieid': id}); + return base64Decode(base64str.substring(23)); + } + Future loadData() async { - final data = - await API.query("video", "readThumbnail", {'Movieid': widget.dta.id}); + Uint8List data; + final id = widget.dta.id; + if (kIsWeb) { + data = await _fetchThumbnail(id); + } else { + final List> prev = + await Db().db().query('previews', where: "id=$id"); + + if (prev.isEmpty) { + data = await _fetchThumbnail(id); + insert(id, data); + Log.d("Adding $id to db"); + } else { + data = prev.first["thumbnail"] as Uint8List; + Log.d("using cached preview for $id"); + } + } final img = Image.memory( - base64Decode(data.substring(23)), + data, width: double.infinity, fit: BoxFit.fitWidth, ); @@ -87,6 +108,54 @@ class _PreviewTileState extends State { ]); } + Widget _buildTile(Image image) { + return ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Stack( + children: [ + Container( + child: Column( + children: [ + Text( + widget.dta.title, + style: TextStyle(fontSize: isTV() ? 8 : 10.5), + overflow: TextOverflow.clip, + maxLines: 1, + ), + image + ], + ), + color: Color(0x6a94a6ff), + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onLongPress: () { + if (widget.onLongPress != null) widget.onLongPress!(image); + }, + onLongPressEnd: (details) { + if (widget.onLongPressEnd != null) widget.onLongPressEnd!(); + }, + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => VideoScreen(metaData: widget.dta), + ), + ); + }, + ), + ), + ), + ), + ], + ), + ); + } + @override Widget build(BuildContext context) { return FutureBuilder( @@ -99,54 +168,7 @@ class _PreviewTileState extends State { if (snapshot.hasError) { return Text("Error"); } else if (snapshot.hasData) { - return ClipRRect( - borderRadius: BorderRadius.circular(20.0), - child: Stack( - children: [ - Container( - child: Column( - children: [ - Text( - widget.dta.title, - style: TextStyle(fontSize: isTV() ? 8 : 10.5), - overflow: TextOverflow.clip, - maxLines: 1, - ), - snapshot.data! - ], - ), - color: Color(0x6a94a6ff), - ), - Positioned.fill( - child: Material( - color: Colors.transparent, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onLongPress: () { - if (widget.onLongPress != null) - widget.onLongPress!(snapshot.data!); - }, - onLongPressEnd: (details) { - if (widget.onLongPressEnd != null) - widget.onLongPressEnd!(); - }, - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - VideoScreen(metaData: widget.dta), - ), - ); - }, - ), - ), - ), - ), - ], - ), - ); + return _buildTile(snapshot.data!); } else { return _buildLoader(); } diff --git a/lib/preview/tag_tile.dart b/lib/preview/tag_tile.dart new file mode 100644 index 0000000..f5ddae7 --- /dev/null +++ b/lib/preview/tag_tile.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import '../types/tag.dart'; + +import '../navigation/video_feed.dart'; + +class TagTile extends StatelessWidget { + const TagTile({Key? key, required this.tag}) : super(key: key); + final Tag tag; + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Scaffold( + appBar: AppBar(title: Text(tag.tagName)), + body: VideoFeed(tag: tag), + ), + ), + ); + }, + style: ElevatedButton.styleFrom(primary: Color(0x6a94a6ff)), + child: SizedBox( + child: Center(child: Text(tag.tagName)), + height: 100, + width: 100, + ), + ); + } +} diff --git a/lib/types/video.dart b/lib/types/video.dart new file mode 100644 index 0000000..93a1e6b --- /dev/null +++ b/lib/types/video.dart @@ -0,0 +1,12 @@ +class VideoT { + int id; + String title; + double ratio; + + VideoT(this.title, this.id, this.ratio); + + factory VideoT.fromJson(dynamic json) { + return VideoT(json['MovieName'] as String, json['MovieId'] as int, + (json['Ratio'] as num).toDouble()); + } +} diff --git a/lib/video_screen/actor_view.dart b/lib/video_screen/info_view.dart similarity index 84% rename from lib/video_screen/actor_view.dart rename to lib/video_screen/info_view.dart index 3298d66..a3a018d 100644 --- a/lib/video_screen/actor_view.dart +++ b/lib/video_screen/info_view.dart @@ -2,21 +2,22 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:openmediacentermobile/screen_loading.dart'; +import 'package:openmediacentermobile/preview/tag_tile.dart'; import 'package:openmediacentermobile/types/video_data.dart'; -import 'package:openmediacentermobile/video_screen/actor_tile.dart'; +import 'package:openmediacentermobile/preview/actor_tile.dart'; import '../api/api.dart'; import '../types/actor.dart'; -class ActorView extends StatefulWidget { - const ActorView({Key? key, required this.vdata}) : super(key: key); +class InfoView extends StatefulWidget { + const InfoView({Key? key, required this.vdata}) : super(key: key); final VideoData vdata; @override - State createState() => _ActorViewState(); + State createState() => _InfoViewState(); } -class _ActorViewState extends State { +class _InfoViewState extends State { late Future> _data; @override @@ -66,7 +67,7 @@ class _ActorViewState extends State { Text("Tags:"), Row( children: widget.vdata.tags - .map((e) => Text(e.tagName)) + .map((e) => TagTile(tag: e)) .toList(growable: false), ) ])); diff --git a/lib/video_screen/videoscreen.dart b/lib/video_screen/videoscreen.dart new file mode 100644 index 0000000..7409a37 --- /dev/null +++ b/lib/video_screen/videoscreen.dart @@ -0,0 +1,165 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:flutter/material.dart'; + +import '../api/api.dart'; +import '../api/token.dart'; +import '../platform.dart'; +import '../screen_loading.dart'; +import '../types/video.dart'; +import '../types/video_data.dart'; +import 'info_view.dart'; + +import 'videoscreen_desktop.dart' + if (dart.library.html) 'videoscreen_mobile.dart'; +import 'videoscreen_mobile.dart'; + +class VideoScreen extends StatefulWidget { + const VideoScreen({Key? key, required this.metaData}) : super(key: key); + final VideoT metaData; + + @override + State createState() => _VideoScreenState(); +} + +class _VideoScreenState extends State { + bool _appBarVisible = true; + Timer? _appBarTimer; + late Future _videoData; + PageController _controller = PageController( + initialPage: 0, + ); + + String url = ""; + + Future loadVideoData() async { + final data = + await API.query("video", "loadVideo", {'MovieId': widget.metaData.id}); + + final d = jsonDecode(data); + final video = VideoData.fromJson(d); + return video; + } + + void initPlayer() async { + final videodata = await _videoData; + + final token = await Token.getInstance().getToken(); + if (token == null) return; + + final baseurl = token.domain; + // todo not static middle path + final path = baseurl + "/videos/vids/" + videodata.movieUrl; + + url = path; + } + + @override + void initState() { + super.initState(); + _videoData = loadVideoData(); + initPlayer(); + _setAppBarTimer(); + } + + @override + void dispose() { + _controller.dispose(); + _appBarTimer?.cancel(); + super.dispose(); + } + + void _setAppBarTimer() { + _appBarTimer?.cancel(); + _appBarTimer = Timer( + Duration(seconds: 3), + () { + setState(() { + _appBarVisible = false; + }); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: FutureBuilder( + future: _videoData, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text("Error"); + } else if (snapshot.hasData) { + return MouseRegion( + onHover: (PointerEvent event) async { + if (isDesktop()) { + if (event.delta.dx != 0 || event.delta.dy != 0) { + setState(() { + _appBarVisible = true; + }); + _setAppBarTimer(); + } + } + }, + child: GestureDetector( + onPanDown: (details) async { + if (_appBarVisible) { + await Future.delayed(Duration(milliseconds: 100)); + setState(() { + _appBarVisible = false; + }); + } else { + if (!isDesktop()) { + setState(() { + _appBarVisible = true; + }); + _setAppBarTimer(); + } + } + }, + behavior: HitTestBehavior.opaque, + child: Stack(children: [ + PageView( + scrollDirection: Axis.vertical, + controller: _controller, + children: [ + Center( + child: isDesktop() + ? VideoScreenDesktop( + url: url, + ) + : VideoScreenMobile( + url: url, + )), + InfoView( + vdata: snapshot.data!, + ) + ]), + if (_appBarVisible) + new Positioned( + top: 0.0, + left: 0.0, + right: 0.0, + child: AppBar( + title: Text(widget.metaData.title), + leading: new IconButton( + icon: new Icon(Icons.arrow_back_ios, + color: Colors.grey), + onPressed: () => Navigator.of(context).pop(), + ), + backgroundColor: + Theme.of(context).primaryColor.withOpacity(0.3), + elevation: 0.0, + ), + ), + ]), + ), + ); + } else { + return ScreenLoading(); + } + }, + ), + ); + } +} diff --git a/lib/video_screen/videoscreen_desktop.dart b/lib/video_screen/videoscreen_desktop.dart index e57fb24..ffae7e1 100644 --- a/lib/video_screen/videoscreen_desktop.dart +++ b/lib/video_screen/videoscreen_desktop.dart @@ -1,198 +1,22 @@ -import 'dart:async'; -import 'dart:convert'; import 'dart:math'; -import 'package:chewie/chewie.dart'; -import "package:dart_vlc/dart_vlc.dart"; +import 'package:dart_vlc/dart_vlc.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:openmediacentermobile/preview/preview_tile.dart'; -import 'package:openmediacentermobile/screen_loading.dart'; -import 'package:openmediacentermobile/video_screen/actor_view.dart'; -import 'package:video_player/video_player.dart'; -import '../api/api.dart'; -import '../api/token.dart'; -import '../platform.dart'; -import '../types/video_data.dart'; - -class VideoScreen extends StatefulWidget { - const VideoScreen({Key? key, required this.metaData}) : super(key: key); - final VideoT metaData; +class VideoScreenDesktop extends StatefulWidget { + const VideoScreenDesktop({Key? key, required this.url}) : super(key: key); + final String url; @override - State createState() => _VideoScreenState(); + State createState() => _VideoScreenDesktopState(); } -class _VideoScreenState extends State { - Player? _player = - isDesktop() ? Player(id: Random().nextInt(0x7fffffff)) : null; - ChewieController? _chewieController; - - bool _appBarVisible = true; - Timer? _appBarTimer; - late Future _videoData; - - Future loadVideoData() async { - final data = - await API.query("video", "loadVideo", {'MovieId': widget.metaData.id}); - - final d = jsonDecode(data); - final video = VideoData.fromJson(d); - return video; - } - - void initPlayer() async { - final videodata = await _videoData; - - final token = await Token.getInstance().getToken(); - if (token == null) return; - - final baseurl = token.domain; - // todo not static middle path - final path = baseurl + "/videos/vids/" + videodata.movieUrl; - - if (isDesktop()) { - final media2 = Media.network(path); - - _player?.open( - media2, - autoStart: true, // default - ); - } else { - final VideoPlayerController _controller = - VideoPlayerController.network(path); - await _controller.initialize(); - - _chewieController = ChewieController( - videoPlayerController: _controller, - autoPlay: true, - looping: true, - allowFullScreen: true, - allowMuting: true, - allowPlaybackSpeedChanging: true, - zoomAndPan: true); - - setState(() {}); - } - } - - @override - void initState() { - super.initState(); - - _videoData = loadVideoData(); - initPlayer(); - - if (isDesktop()) { - RawKeyboard.instance.addListener((value) { - if (value.logicalKey == LogicalKeyboardKey.arrowRight) { - _player - ?.seek(_player!.position.position! + const Duration(seconds: 5)); - } else if (value.logicalKey == LogicalKeyboardKey.arrowLeft) { - _player - ?.seek(_player!.position.position! + const Duration(seconds: -5)); - } - }); - } - - _setAppBarTimer(); - } - - @override - void dispose() { - if (isDesktop()) { - _player?.dispose(); - } else { - _chewieController?.videoPlayerController.dispose(); - _chewieController?.dispose(); - } - _controller.dispose(); - _appBarTimer?.cancel(); - super.dispose(); - } - - void _setAppBarTimer() { - _appBarTimer?.cancel(); - _appBarTimer = Timer( - Duration(seconds: 3), - () { - setState(() { - _appBarVisible = false; - }); - }, - ); - } - - PageController _controller = PageController( - initialPage: 0, - ); +class _VideoScreenDesktopState extends State { + Player _player = Player(id: Random().nextInt(0x7fffffff)); @override Widget build(BuildContext context) { - return Scaffold( - body: FutureBuilder( - future: _videoData, - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - return Text("Error"); - } else if (snapshot.hasData) { - return MouseRegion( - onHover: (PointerEvent event) async { - if (event.delta.dx != 0 || event.delta.dy != 0) { - if (_appBarVisible) { - await Future.delayed(Duration(milliseconds: 100)); - setState(() { - _appBarVisible = false; - }); - } else { - setState(() { - _appBarVisible = true; - }); - _setAppBarTimer(); - } - } - }, - child: Stack(children: [ - PageView( - scrollDirection: Axis.vertical, - controller: _controller, - children: [ - Center( - child: - isDesktop() ? videoDesktop() : videoNotDesktop()), - ActorView( - vdata: snapshot.data!, - ) - ]), - if (_appBarVisible) - new Positioned( - top: 0.0, - left: 0.0, - right: 0.0, - child: AppBar( - title: Text(widget.metaData.title), - leading: new IconButton( - icon: - new Icon(Icons.arrow_back_ios, color: Colors.grey), - onPressed: () => Navigator.of(context).pop(), - ), - backgroundColor: - Theme.of(context).primaryColor.withOpacity(0.3), - elevation: 0.0, - ), - ), - ]), - ); - } else { - return ScreenLoading(); - } - }, - ), - ); - } - - Widget videoDesktop() { return Video( player: _player, scale: 1.0, // default @@ -201,16 +25,29 @@ class _VideoScreenState extends State { ); } - Widget videoNotDesktop() { - if (_chewieController == null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: const [CircularProgressIndicator(), Text("loading...")], - ); - } - return Chewie( - controller: _chewieController!, + @override + void initState() { + super.initState(); + + final media2 = Media.network(widget.url); + + _player.open( + media2, + autoStart: true, // default ); + + RawKeyboard.instance.addListener((value) { + if (value.logicalKey == LogicalKeyboardKey.arrowRight) { + _player.seek(_player.position.position! + const Duration(seconds: 5)); + } else if (value.logicalKey == LogicalKeyboardKey.arrowLeft) { + _player.seek(_player.position.position! + const Duration(seconds: -5)); + } + }); + } + + @override + void dispose() { + super.dispose(); + _player.dispose(); } } diff --git a/lib/video_screen/videoscreen_mobile.dart b/lib/video_screen/videoscreen_mobile.dart new file mode 100644 index 0000000..61e5e41 --- /dev/null +++ b/lib/video_screen/videoscreen_mobile.dart @@ -0,0 +1,59 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +class VideoScreenMobile extends StatefulWidget { + const VideoScreenMobile({Key? key, required this.url}) : super(key: key); + final String url; + + @override + State createState() => _VideoScreenMobileState(); +} + +class _VideoScreenMobileState extends State { + ChewieController? _chewieController; + + @override + Widget build(BuildContext context) { + if (_chewieController == null) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: const [CircularProgressIndicator(), Text("loading...")], + ); + } + return Chewie( + controller: _chewieController!, + ); + } + + @override + void dispose() { + super.dispose(); + _chewieController?.videoPlayerController.dispose(); + _chewieController?.dispose(); + } + + @override + void initState() { + super.initState(); + _init(); + } + + void _init() async { + final VideoPlayerController _controller = + VideoPlayerController.network(widget.url); + await _controller.initialize(); + + _chewieController = ChewieController( + videoPlayerController: _controller, + autoPlay: true, + looping: true, + allowFullScreen: true, + allowMuting: true, + allowPlaybackSpeedChanging: true, + zoomAndPan: true); + + setState(() {}); + } +} diff --git a/lib/video_screen/videoscreen_web.dart b/lib/video_screen/videoscreen_web.dart deleted file mode 100644 index bc7e221..0000000 --- a/lib/video_screen/videoscreen_web.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; - -import '../api/api.dart'; -import '../api/token.dart'; -import '../log/log.dart'; - -class VideoScreen extends StatefulWidget { - const VideoScreen({Key? key, required this.videoID}) : super(key: key); - final int videoID; - - @override - State createState() => _VideoScreenState(); -} - -class _VideoScreenState extends State { - void loadData() async { - final data = - await API.query("video", "loadVideo", {'MovieId': widget.videoID}); - - final d = jsonDecode(data); - - final url = d["MovieUrl"]; - final token = await Token.getInstance().getToken(); - if (token == null) return; - - final baseurl = token.domain; - // todo not static middle path - final String path = baseurl + "/videos/vids/" + url; - Log.d(path); - } - - @override - void initState() { - super.initState(); - - loadData(); - - // todo hide appbar after some seonds - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Second Route'), - ), - body: const Center(child: Text("Todo to implement")), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index ea0d1ff..9d1459f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -290,7 +290,7 @@ packages: source: hosted version: "1.0.0" path: - dependency: transitive + dependency: "direct main" description: name: path url: "https://pub.dartlang.org" @@ -385,6 +385,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + sqflite: + dependency: "direct main" + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1+1" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1+1" + sqlite3: + dependency: transitive + description: + name: sqlite3 + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" stack_trace: dependency: transitive description: @@ -406,6 +434,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+2" term_glyph: dependency: transitive description: @@ -527,4 +562,4 @@ packages: version: "0.2.0+1" sdks: dart: ">=2.17.0-206.0.dev <3.0.0" - flutter: ">=2.8.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 041ab73..7f5f6e2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,9 @@ dependencies: device_info_plus: ^3.2.3 video_player: ^2.3.0 chewie: ^1.3.2 + sqflite: ^2.0.3+1 + path: ^1.8.1 + sqflite_common_ffi: ^2.1.1+1 dev_dependencies: flutter_test: