diff --git a/lib/main.dart b/lib/main.dart index f341241..89ff36b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:f0ckapp/mediagrid.dart'; +import 'package:f0ckapp/screens/MediaGrid.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/models/mediaitem.dart b/lib/models/MediaItem.dart similarity index 100% rename from lib/models/mediaitem.dart rename to lib/models/MediaItem.dart diff --git a/lib/screens/detailview.dart b/lib/screens/DetailView.dart similarity index 89% rename from lib/screens/detailview.dart rename to lib/screens/DetailView.dart index 8b1975b..b55b8b7 100644 --- a/lib/screens/detailview.dart +++ b/lib/screens/DetailView.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:f0ckapp/models/mediaitem.dart'; -import 'package:f0ckapp/services/api.dart'; -import 'package:f0ckapp/widgets/video_widget.dart'; +import 'package:f0ckapp/models/MediaItem.dart'; +import 'package:f0ckapp/services/Api.dart'; +import 'package:f0ckapp/widgets/VideoWidget.dart'; import 'package:f0ckapp/utils/SmartRefreshIndicator.dart'; +import 'package:f0ckapp/utils/PageTransformer.dart'; class DetailView extends StatefulWidget { final int initialItemId; @@ -109,20 +110,18 @@ class _DetailViewState extends State { title: Text('f0ck #$currentItemId (${widget.type})'), centerTitle: true, ), - body: PageView.builder( + body: PageTransformer( controller: _pageController, - itemCount: mediaItems.length, - itemBuilder: (context, index) { - final MediaItem item = mediaItems[index]; + pages: mediaItems.map((item) { return Scaffold( body: SafeArea( child: SmartRefreshIndicator( onRefresh: _refreshMediaItem, - child: _buildMediaItem(item) + child: _buildMediaItem(item), ), ), ); - }, + }).toList(), ), ); } @@ -130,6 +129,7 @@ class _DetailViewState extends State { Widget _buildMediaItem(MediaItem item) { return SingleChildScrollView( child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ if (item.mime.startsWith('image')) CachedNetworkImage( @@ -145,7 +145,7 @@ class _DetailViewState extends State { item.mime, style: const TextStyle(color: Colors.white, fontSize: 18), ), - const SizedBox(height: 10), + const SizedBox(height: 10, width: double.infinity), Wrap( alignment: WrapAlignment.center, spacing: 5.0, @@ -155,7 +155,7 @@ class _DetailViewState extends State { backgroundColor: switch (tag.id) { 1 => Colors.green, 2 => Colors.red, - _ => const Color(0xFF090909) + _ => const Color(0xFF090909), }, labelStyle: const TextStyle(color: Colors.white), ); diff --git a/lib/mediagrid.dart b/lib/screens/MediaGrid.dart similarity index 97% rename from lib/mediagrid.dart rename to lib/screens/MediaGrid.dart index 2f1a279..a9d7fb6 100644 --- a/lib/mediagrid.dart +++ b/lib/screens/MediaGrid.dart @@ -1,8 +1,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:f0ckapp/services/api.dart'; -import 'package:f0ckapp/models/mediaitem.dart'; -import 'package:f0ckapp/screens/detailview.dart'; +import 'package:f0ckapp/services/Api.dart'; +import 'package:f0ckapp/models/MediaItem.dart'; +import 'package:f0ckapp/screens/DetailView.dart'; import 'dart:async'; class MediaGrid extends StatefulWidget { @@ -14,7 +14,7 @@ class MediaGrid extends StatefulWidget { class _MediaGridState extends State { final ScrollController _scrollController = ScrollController(); - final String _version = '1.0.21+21'; + final String _version = '1.0.22+22'; List mediaItems = []; bool isLoading = false; Timer? _debounceTimer; @@ -252,7 +252,7 @@ class _MediaGridState extends State { CachedNetworkImage( imageUrl: item.thumbnailUrl, fit: BoxFit.cover, - placeholder: (context, url) => CircularProgressIndicator(), + placeholder: (context, url) => SizedBox.shrink(), errorWidget: (context, url, error) => Icon(Icons.error), ), Align( diff --git a/lib/services/api.dart b/lib/services/Api.dart similarity index 96% rename from lib/services/api.dart rename to lib/services/Api.dart index 1f23d96..c7a1a56 100644 --- a/lib/services/api.dart +++ b/lib/services/Api.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; -import 'package:f0ckapp/models/mediaitem.dart'; +import 'package:f0ckapp/models/MediaItem.dart'; Future> fetchMedia({ String? older, diff --git a/lib/utils/PageTransformer.dart b/lib/utils/PageTransformer.dart new file mode 100644 index 0000000..6abe1f0 --- /dev/null +++ b/lib/utils/PageTransformer.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class PageTransformer extends StatelessWidget { + final List pages; + final PageController controller; + + const PageTransformer({ + super.key, + required this.pages, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return PageView.builder( + controller: controller, + itemCount: pages.length, + itemBuilder: (context, index) { + return _buildPage(pages[index], index); + }, + ); + } + + Widget _buildPage(Widget page, int index) { + return AnimatedBuilder( + animation: controller, + builder: (context, child) { + double value = 1.0; + if (controller.position.haveDimensions) { + value = controller.page! - index; + value = (1 - (value.abs() * 0.5)).clamp(0.0, 1.0); + } + return Transform( + transform: Matrix4.identity()..scaleByDouble(value, value, value, 1), + alignment: Alignment.center, + child: child, + ); + }, + child: page, + ); + } +} diff --git a/lib/widgets/video_overlay.dart b/lib/widgets/VideoOverlay.dart similarity index 79% rename from lib/widgets/video_overlay.dart rename to lib/widgets/VideoOverlay.dart index ef91869..dc9e90a 100644 --- a/lib/widgets/video_overlay.dart +++ b/lib/widgets/VideoOverlay.dart @@ -3,7 +3,13 @@ import 'package:video_player/video_player.dart'; class VideoControlsOverlay extends StatelessWidget { final VideoPlayerController controller; - const VideoControlsOverlay({super.key, required this.controller}); + final VoidCallback button; + + const VideoControlsOverlay({ + super.key, + required this.controller, + required this.button, + }); @override Widget build(BuildContext context) { @@ -15,22 +21,25 @@ class VideoControlsOverlay extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ _ControlButton(Icons.replay_10, () { + button(); controller.seekTo( controller.value.position - Duration(seconds: 10), ); }), - SizedBox(width: 32), + SizedBox(width: 40), _ControlButton( controller.value.isPlaying ? Icons.pause : Icons.play_arrow, () { + button(); controller.value.isPlaying ? controller.pause() : controller.play(); }, size: 64, ), - SizedBox(width: 32), + SizedBox(width: 40), _ControlButton(Icons.forward_10, () { + button(); controller.seekTo( controller.value.position + Duration(seconds: 10), ); @@ -38,7 +47,7 @@ class VideoControlsOverlay extends StatelessWidget { ], ), ), - _ProgressIndicator(controller: controller), + _ProgressIndicator(controller: controller) ], ); } @@ -68,6 +77,7 @@ class _ControlButton extends StatelessWidget { class _ProgressIndicator extends StatelessWidget { final VideoPlayerController controller; + const _ProgressIndicator({required this.controller}); @override @@ -77,18 +87,14 @@ class _ProgressIndicator extends StatelessWidget { child: Stack( clipBehavior: Clip.none, children: [ - Container( - height: 20, - alignment: Alignment.bottomCenter, - color: Colors.transparent, - child: VideoProgressIndicator( - controller, - allowScrubbing: true, - colors: VideoProgressColors( - playedColor: Colors.red, - backgroundColor: Colors.grey, - bufferedColor: Colors.white54, - ), + VideoProgressIndicator( + controller, + allowScrubbing: true, + padding: const EdgeInsets.only(top: 25.0), + colors: VideoProgressColors( + playedColor: Colors.red, + backgroundColor: Colors.grey, + bufferedColor: Colors.white54, ), ), Positioned( @@ -123,8 +129,6 @@ class _ProgressIndicator extends StatelessWidget { String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); - final minutes = twoDigits(duration.inMinutes.remainder(60)); - final seconds = twoDigits(duration.inSeconds.remainder(60)); - return "$minutes:$seconds"; + return "${twoDigits(duration.inMinutes % 60)}:${twoDigits(duration.inSeconds % 60)}"; } } diff --git a/lib/widgets/video_widget.dart b/lib/widgets/VideoWidget.dart similarity index 78% rename from lib/widgets/video_widget.dart rename to lib/widgets/VideoWidget.dart index de665c0..2aa59d6 100644 --- a/lib/widgets/video_widget.dart +++ b/lib/widgets/VideoWidget.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:f0ckapp/models/mediaitem.dart'; -import 'package:f0ckapp/widgets/video_overlay.dart'; +import 'package:f0ckapp/models/MediaItem.dart'; +import 'package:f0ckapp/widgets/VideoOverlay.dart'; class VideoWidget extends StatefulWidget { final MediaItem details; @@ -17,6 +17,7 @@ class VideoWidget extends StatefulWidget { class _VideoWidgetState extends State { late VideoPlayerController _controller; bool _showControls = false; + Timer? _hideControlsTimer; @override void initState() { @@ -39,9 +40,23 @@ class _VideoWidgetState extends State { @override void dispose() { _controller.dispose(); + _hideControlsTimer?.cancel(); super.dispose(); } + void _onTap({bool ctrlButton = false}) { + if (!ctrlButton) { + setState(() => _showControls = !_showControls); + } + + if (_showControls) { + _hideControlsTimer?.cancel(); + _hideControlsTimer = Timer(Duration(seconds: 5), () { + setState(() => _showControls = false); + }); + } + } + @override Widget build(BuildContext context) { bool isAudio = widget.details.mime.startsWith('audio'); @@ -57,11 +72,7 @@ class _VideoWidgetState extends State { alignment: Alignment.center, children: [ GestureDetector( - onTap: () { - setState(() { - _showControls = !_showControls; - }); - }, + onTap: _onTap, child: isAudio ? CachedNetworkImage( imageUrl: widget.details.coverUrl, @@ -78,15 +89,7 @@ class _VideoWidgetState extends State { : Center(child: CircularProgressIndicator()), ), if (_controller.value.isInitialized && _showControls) ...[ - IgnorePointer( - ignoring: true, - child: Container( - color: Colors.black.withValues(alpha: 0.5), - width: double.infinity, - height: double.infinity, - ), - ), - VideoControlsOverlay(controller: _controller), + VideoControlsOverlay(controller: _controller, button: () => _onTap(ctrlButton: true)), ], ], ), diff --git a/pubspec.yaml b/pubspec.yaml index de48baf..571fcfa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.21+21 +version: 1.0.22+22 environment: sdk: ^3.9.0-100.2.beta