feat: swipeable media pager, favorite artists, optimized video preview, Gitea updates and v2.0.2 release

This commit is contained in:
2026-09-03 18:09:38 +03:00
parent cc5a1a4f82
commit e419c7ad78
23 changed files with 837 additions and 266 deletions
@@ -1,3 +1,4 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -39,12 +40,14 @@ class PostDetailsScreen extends ConsumerWidget {
required this.providerId,
required this.postId,
this.initialPost,
this.postsList,
super.key,
});
final String providerId;
final String postId;
final Post? initialPost;
final List<Post>? postsList;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -57,8 +60,9 @@ class PostDetailsScreen extends ConsumerWidget {
final settings =
ref.watch(appSettingsProvider).value ?? AppSettings.defaults;
final strings = ref.watch(appStringsProvider);
final feedPosts =
ref.watch(feedControllerProvider).value?.posts ?? const <Post>[];
final feedPosts = postsList ??
ref.watch(feedControllerProvider).value?.posts ??
const <Post>[];
final favoriteKeys = ref.watch(favoriteKeysProvider).value ?? <String>{};
return AdaptiveScaffold(
title: strings.post,
@@ -698,17 +702,26 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
@override
Widget build(BuildContext context) {
return PageView.builder(
controller: _controller,
physics: _mediaGestureLocked
? const NeverScrollableScrollPhysics()
: const PageScrollPhysics(),
itemCount: widget.posts.length,
onPageChanged: (index) {
_prefetchAround(index);
},
itemBuilder: (context, index) {
final initialPost = widget.posts[index];
return ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(
dragDevices: {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
PointerDeviceKind.trackpad,
PointerDeviceKind.stylus,
},
),
child: PageView.builder(
controller: _controller,
physics: _mediaGestureLocked
? const NeverScrollableScrollPhysics()
: const PageScrollPhysics(),
itemCount: widget.posts.length,
onPageChanged: (index) {
_prefetchAround(index);
},
itemBuilder: (context, index) {
final initialPost = widget.posts[index];
return Consumer(
builder: (context, ref, _) {
final args = PostDetailsArgs(
@@ -735,8 +748,9 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
},
);
},
);
}
),
);
}
void _setMediaGestureLocked(bool locked) {
if (_mediaGestureLocked == locked) return;
@@ -219,9 +219,54 @@ class _PostMediaViewerState extends State<PostMediaViewer>
);
},
);
final child = _ZoomableImage(
onGestureLockChanged: widget.onMediaGestureLockChanged,
child: image,
final child = Stack(
alignment: Alignment.center,
children: [
_ZoomableImage(
onGestureLockChanged: widget.onMediaGestureLockChanged,
child: image,
),
if (_imageUrls.length > 1)
Positioned(
bottom: 14,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.65),
borderRadius: BorderRadius.circular(14),
border: Border.all(color: Colors.white24, width: 0.8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (_imageIndex > 0)
GestureDetector(
onTap: () => setState(() => _imageIndex--),
child: const Icon(Icons.chevron_left_rounded,
size: 20, color: Colors.white),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
'${_imageIndex + 1} / ${_imageUrls.length}',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
if (_imageIndex < _imageUrls.length - 1)
GestureDetector(
onTap: () => setState(() => _imageIndex++),
child: const Icon(Icons.chevron_right_rounded,
size: 20, color: Colors.white),
),
],
),
),
),
],
);
return LayoutBuilder(
builder: (context, constraints) {