diff --git a/lib/DrawerPage.dart b/lib/DrawerPage.dart index c4219c1..c69b047 100644 --- a/lib/DrawerPage.dart +++ b/lib/DrawerPage.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:openmediacentermobile/shufflescreen.dart'; -import 'package:openmediacentermobile/video_feed.dart'; +import 'navigation/categorie_screen.dart'; +import 'navigation/shufflescreen.dart'; +import 'navigation/video_feed.dart'; import 'api/token.dart'; import 'login/logincontext.dart'; @@ -14,7 +15,7 @@ class DrawerPage extends StatefulWidget { _DrawerPageState createState() => _DrawerPageState(); } -enum Section { HOME, SHUFFLE, LOGOUT } +enum Section { HOME, SHUFFLE, LOGOUT, CATEGORIE } class _DrawerPageState extends State { Section _sec = Section.HOME; @@ -39,6 +40,11 @@ class _DrawerPageState extends State { body = const Text("also todo"); title = "Settings"; break; + + case Section.CATEGORIE: + body = CategorieScreen(); + title = "Categories"; + break; } final loginCtx = LoginContext.of(context); @@ -78,6 +84,16 @@ class _DrawerPageState extends State { Navigator.pop(context); }, ), + ListTile( + title: const Text('Categories'), + leading: const Icon(Icons.category), + onTap: () { + setState(() { + _sec = Section.CATEGORIE; + }); + Navigator.pop(context); + }, + ), ListTile( title: const Text('Settings'), leading: const Icon(Icons.settings), diff --git a/lib/navigation/categorie_screen.dart b/lib/navigation/categorie_screen.dart new file mode 100644 index 0000000..c1aa269 --- /dev/null +++ b/lib/navigation/categorie_screen.dart @@ -0,0 +1,82 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import '../navigation/video_feed.dart'; +import '../screen_loading.dart'; + +import '../api/api.dart'; +import '../types/tag.dart'; + +class CategorieScreen extends StatefulWidget { + const CategorieScreen({Key? key}) : super(key: key); + + @override + State createState() => _CategorieScreenState(); +} + +class _CategorieScreenState extends State { + late Future> _categories; + + Future> loadVideoData() async { + final data = await API.query("tags", "getAllTags", {}); + + final d = (jsonDecode(data) ?? []) as List; + final tags = d.map((e) => Tag.fromJson(e)).toList(growable: false); + return tags; + } + + @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: 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), + ), + ); + } else { + return ScreenLoading(); + } + }, + ); + } +} diff --git a/lib/shufflescreen.dart b/lib/navigation/shufflescreen.dart similarity index 89% rename from lib/shufflescreen.dart rename to lib/navigation/shufflescreen.dart index cb66419..2278ffb 100644 --- a/lib/shufflescreen.dart +++ b/lib/navigation/shufflescreen.dart @@ -2,11 +2,11 @@ import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:openmediacentermobile/preview_grid.dart'; -import 'preview_tile.dart'; +import 'package:openmediacentermobile/preview/preview_grid.dart'; +import '../preview/preview_tile.dart'; -import 'api/api.dart'; -import 'platform.dart'; +import '../api/api.dart'; +import '../platform.dart'; class ShuffleScreen extends StatefulWidget { const ShuffleScreen({Key? key}) : super(key: key); diff --git a/lib/video_feed.dart b/lib/navigation/video_feed.dart similarity index 66% rename from lib/video_feed.dart rename to lib/navigation/video_feed.dart index 2ac6b1a..c3486ea 100644 --- a/lib/video_feed.dart +++ b/lib/navigation/video_feed.dart @@ -2,13 +2,15 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:openmediacentermobile/api/api.dart'; -import 'package:openmediacentermobile/preview_grid.dart'; +import 'package:openmediacentermobile/preview/preview_grid.dart'; -import 'log/log.dart'; -import 'preview_tile.dart'; +import '../log/log.dart'; +import '../preview/preview_tile.dart'; +import '../types/tag.dart'; class VideoFeed extends StatefulWidget { - const VideoFeed({Key? key}) : super(key: key); + const VideoFeed({Key? key, this.tag}) : super(key: key); + final Tag? tag; @override State createState() { @@ -18,7 +20,8 @@ class VideoFeed extends StatefulWidget { class VideoFeedState extends State { Future> loadData() async { - final data = await API.query("video", "getMovies", {'Tag': 1, 'Sort': 0}); + final data = await API.query( + "video", "getMovies", {'Tag': widget.tag?.tagId ?? 1, 'Sort': 0}); final d = jsonDecode(data); diff --git a/lib/preview_grid.dart b/lib/preview/preview_grid.dart similarity index 80% rename from lib/preview_grid.dart rename to lib/preview/preview_grid.dart index 81056ba..950fd59 100644 --- a/lib/preview_grid.dart +++ b/lib/preview/preview_grid.dart @@ -3,7 +3,7 @@ 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_tile.dart'; +import 'package:openmediacentermobile/preview/preview_tile.dart'; import 'package:openmediacentermobile/screen_loading.dart'; class PreviewGrid extends StatefulWidget { @@ -42,18 +42,26 @@ class _PreviewGridState extends State { Widget build(BuildContext context) { final double width = MediaQuery.of(context).size.width; - return FutureBuilder>( - future: _data, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return Text("Error"); - } else if (snapshot.hasData) { - return _mainGrid(snapshot.data!, width); - } else { - return ScreenLoading(); - } - }, - ); + return RefreshIndicator( + onRefresh: () async { + loadData(); + await Future.delayed(Duration(milliseconds: 600)); + }, + triggerMode: RefreshIndicatorTriggerMode.anywhere, + color: Colors.purple, + child: FutureBuilder>( + future: _data, + builder: + (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasError) { + return Text("Error"); + } else if (snapshot.hasData) { + return _mainGrid(snapshot.data!, width); + } else { + return ScreenLoading(); + } + }, + )); } Widget _mainGrid(List data, double width) { diff --git a/lib/preview_tile.dart b/lib/preview/preview_tile.dart similarity index 87% rename from lib/preview_tile.dart rename to lib/preview/preview_tile.dart index ca0d517..ccb409b 100644 --- a/lib/preview_tile.dart +++ b/lib/preview/preview_tile.dart @@ -5,8 +5,8 @@ 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 'api/api.dart'; -import 'platform.dart'; +import '../api/api.dart'; +import '../platform.dart'; // todo put this type in sperate class! class VideoT { @@ -71,11 +71,31 @@ class _PreviewTileState extends State { return img; } + Widget _buildLoader() { + return Column(children: const [ + SizedBox(height: 50), + SizedBox( + width: 60, + height: 60, + child: CircularProgressIndicator(), + ), + Padding( + padding: EdgeInsets.only(top: 16), + child: Text('Awaiting result...'), + ), + SizedBox(height: 50), + ]); + } + @override Widget build(BuildContext context) { return FutureBuilder( future: _preview, // a previously-obtained Future or null builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return _buildLoader(); + } + if (snapshot.hasError) { return Text("Error"); } else if (snapshot.hasData) { @@ -128,19 +148,7 @@ class _PreviewTileState extends State { ), ); } else { - return Column(children: const [ - SizedBox(height: 100), - SizedBox( - width: 60, - height: 60, - child: CircularProgressIndicator(), - ), - Padding( - padding: EdgeInsets.only(top: 16), - child: Text('Awaiting result...'), - ), - SizedBox(height: 100), - ]); + return _buildLoader(); } }, ); diff --git a/lib/types/actor.dart b/lib/types/actor.dart new file mode 100644 index 0000000..87ae3a2 --- /dev/null +++ b/lib/types/actor.dart @@ -0,0 +1,17 @@ +class Actor { + int actorId; + String name; + String thumbnail; + + Actor(this.actorId, this.name, this.thumbnail); + + factory Actor.fromJson(dynamic json) { + return Actor(json['ActorId'] as int, json['Name'] as String, + json['Thumbnail'] as String); + } + + @override + String toString() { + return 'Actor{ActorId: $actorId, Name: $name, Thumbnail: $thumbnail}'; + } +} diff --git a/lib/types/tag.dart b/lib/types/tag.dart new file mode 100644 index 0000000..a6ed416 --- /dev/null +++ b/lib/types/tag.dart @@ -0,0 +1,15 @@ +class Tag { + String tagName; + int tagId; + + Tag(this.tagName, this.tagId); + + @override + String toString() { + return 'Tag{tagName: $tagName, tagId: $tagId}'; + } + + factory Tag.fromJson(dynamic json) { + return Tag(json['TagName'] as String, json['TagId'] as int); + } +} diff --git a/lib/types/video_data.dart b/lib/types/video_data.dart new file mode 100644 index 0000000..637f374 --- /dev/null +++ b/lib/types/video_data.dart @@ -0,0 +1,54 @@ +import 'tag.dart'; +import 'actor.dart'; + +class VideoData { + String movieName; + int movieId; + String movieUrl; + String poster; + int likes; + int quality; + int length; + + List tags; + List suggestedTags; + List actors; + + VideoData( + this.movieName, + this.movieId, + this.movieUrl, + this.poster, + this.likes, + this.quality, + this.length, + this.tags, + this.suggestedTags, + this.actors); + + @override + String toString() { + return 'VideoData{movieName: $movieName, movieId: $movieId, movieUrl: $movieUrl, poster: $poster, likes: $likes, quality: $quality, length: $length, tags: $tags, suggestedTags: $suggestedTags, actors: $actors}'; + } + + factory VideoData.fromJson(dynamic json) { + return VideoData( + json['MovieName'] as String, + json['MovieId'] as int, + json['MovieUrl'] as String, + json['Poster'] as String, + json['Likes'] as int, + json['Quality'] as int, + json['Length'] as int, + ((json['Tags'] ?? []) as List) + .map((e) => Tag.fromJson(e)) + .toList(growable: false), + ((json['SuggestedTag'] ?? []) as List) + .map((e) => Tag.fromJson(e)) + .toList(growable: false), + ((json['Actors'] ?? []) as List) + .map((e) => Actor.fromJson(e)) + .toList(growable: false), + ); + } +} diff --git a/lib/video_screen/actor_tile.dart b/lib/video_screen/actor_tile.dart new file mode 100644 index 0000000..f77448b --- /dev/null +++ b/lib/video_screen/actor_tile.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +import '../platform.dart'; +import '../types/actor.dart'; + +class ActorTile extends StatefulWidget { + const ActorTile({Key? key, required this.actor}) : super(key: key); + final Actor actor; + + @override + State createState() => _ActorTileState(); +} + +class _ActorTileState extends State { + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Stack( + children: [ + Container( + child: Column( + children: [ + Text( + widget.actor.name, + style: TextStyle(fontSize: isTV() ? 8 : 10.5), + overflow: TextOverflow.clip, + maxLines: 1, + ), + SizedBox( + height: 100, + width: 100, + ) + ], + ), + color: Color(0x6a94a6ff), + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onLongPress: () {}, + onLongPressEnd: (details) {}, + child: InkWell( + onTap: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => + // VideoScreen(metaData: widget.dta), + // ), + // ); + }, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/video_screen/actor_view.dart b/lib/video_screen/actor_view.dart index 3f2d2f3..3298d66 100644 --- a/lib/video_screen/actor_view.dart +++ b/lib/video_screen/actor_view.dart @@ -2,12 +2,15 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:openmediacentermobile/screen_loading.dart'; +import 'package:openmediacentermobile/types/video_data.dart'; +import 'package:openmediacentermobile/video_screen/actor_tile.dart'; import '../api/api.dart'; +import '../types/actor.dart'; class ActorView extends StatefulWidget { - const ActorView({Key? key, required this.videoId}) : super(key: key); - final int videoId; + const ActorView({Key? key, required this.vdata}) : super(key: key); + final VideoData vdata; @override State createState() => _ActorViewState(); @@ -26,7 +29,7 @@ class _ActorViewState extends State { Future> loadData() async { final data = await API - .query("actor", "getActorsOfVideo", {'MovieId': widget.videoId}); + .query("actor", "getActorsOfVideo", {'MovieId': widget.vdata.movieId}); if (data == 'null') { return []; } @@ -46,11 +49,27 @@ class _ActorViewState extends State { return Text("Error"); } else if (snapshot.hasData) { final actors = snapshot.data; - if (actors?.isEmpty ?? true) { - return Text("no actors available"); - } else { - return Column(children: _renderActors(snapshot.data!)); - } + return Padding( + padding: EdgeInsets.only(left: 10, right: 10, top: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Likes: ${widget.vdata.likes}"), + Text("Quality: ${widget.vdata.quality}"), + Text("Length: ${widget.vdata.length}sec"), + Text("Actors:"), + actors?.isEmpty ?? true + ? Text("no actors available") + : Row( + children: _renderActors(snapshot.data!), + ), + Text("Tags:"), + Row( + children: widget.vdata.tags + .map((e) => Text(e.tagName)) + .toList(growable: false), + ) + ])); } else { return ScreenLoading(); } @@ -59,29 +78,6 @@ class _ActorViewState extends State { } List _renderActors(List actors) { - return actors - .map((e) => Text( - e.name, - style: TextStyle(color: Colors.black), - )) - .toList(growable: false); - } -} - -class Actor { - int actorId; - String name; - String thumbnail; - - Actor(this.actorId, this.name, this.thumbnail); - - factory Actor.fromJson(dynamic json) { - return Actor(json['ActorId'] as int, json['Name'] as String, - json['Thumbnail'] as String); - } - - @override - String toString() { - return 'Actor{ActorId: $actorId, Name: $name, Thumbnail: $thumbnail}'; + return actors.map((e) => ActorTile(actor: e)).toList(growable: false); } } diff --git a/lib/video_screen/videoscreen_desktop.dart b/lib/video_screen/videoscreen_desktop.dart index 3e4017d..e57fb24 100644 --- a/lib/video_screen/videoscreen_desktop.dart +++ b/lib/video_screen/videoscreen_desktop.dart @@ -6,13 +6,15 @@ import 'package:chewie/chewie.dart'; import "package:dart_vlc/dart_vlc.dart"; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:openmediacentermobile/preview_tile.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); @@ -29,20 +31,26 @@ class _VideoScreenState extends State { bool _appBarVisible = true; Timer? _appBarTimer; + late Future _videoData; - void loadData() async { + 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 url = d["MovieUrl"]; final token = await Token.getInstance().getToken(); if (token == null) return; final baseurl = token.domain; // todo not static middle path - final path = baseurl + "/videos/vids/" + url; + final path = baseurl + "/videos/vids/" + videodata.movieUrl; if (isDesktop()) { final media2 = Media.network(path); @@ -73,6 +81,9 @@ class _VideoScreenState extends State { void initState() { super.initState(); + _videoData = loadVideoData(); + initPlayer(); + if (isDesktop()) { RawKeyboard.instance.addListener((value) { if (value.logicalKey == LogicalKeyboardKey.arrowRight) { @@ -85,7 +96,6 @@ class _VideoScreenState extends State { }); } - loadData(); _setAppBarTimer(); } @@ -121,42 +131,63 @@ class _VideoScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - body: MouseRegion( - onHover: (PointerEvent event) { - if (event.delta.dx != 0 || event.delta.dy != 0) { - setState(() { - _appBarVisible = true; - }); - _setAppBarTimer(); + 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(); } }, - child: Stack(children: [ - PageView( - scrollDirection: Axis.vertical, - controller: _controller, - children: [ - Center(child: isDesktop() ? videoDesktop() : videoNotDesktop()), - ActorView( - videoId: widget.metaData.id, - ) - ]), - 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, - ), - ), - ]), ), ); }