Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c91af8790 | ||
|
|
5434c5dc7b |
@@ -1,4 +1,13 @@
|
||||
const prismaChangelog = [
|
||||
PrismaChange(
|
||||
version: '3.7.2',
|
||||
title: 'Режим непрерывного чтения комиксов и серий e621',
|
||||
bullets: [
|
||||
'Мгновенное открытие комикса целиком: по нажатию «Читать весь комикс» загружаются все страницы серии в оригинальном порядке и открываются в полноэкранном ридере со свайпом (влево-вправо на мобильных, стрелками и кнопками на ПК).',
|
||||
'Плавающий бейдж страниц: во время чтения комикса отображается аккуратный стеклянный индикатор номера страницы («Комикс • Стр. X из Y»).',
|
||||
'Быстрое оглавление: отдельная кнопка сетки страниц для перехода на любую конкретную главу или страницу без потери последовательности чтения.',
|
||||
],
|
||||
),
|
||||
PrismaChange(
|
||||
version: '3.7.1',
|
||||
title: 'Вкладка «e621» в Избранном и исправление листания комиксов',
|
||||
|
||||
@@ -123,6 +123,52 @@ class E621Provider
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<List<Post>> getPoolPosts(String poolId) async {
|
||||
final pool = await getPool(poolId);
|
||||
if (pool == null || pool.postIds.isEmpty) return const [];
|
||||
|
||||
final fetchedMap = <String, Post>{};
|
||||
int page = 1;
|
||||
while (true) {
|
||||
await _throttle();
|
||||
try {
|
||||
final response = await _dio.get<dynamic>(
|
||||
'/posts.json',
|
||||
queryParameters: {
|
||||
'page': page,
|
||||
'limit': isAuthorized ? 320 : 75,
|
||||
'tags': 'pool:$poolId',
|
||||
..._queryParameters,
|
||||
},
|
||||
);
|
||||
final items = E621Mapper.postsFromResponse(
|
||||
response.data,
|
||||
providerId: id,
|
||||
providerName: name,
|
||||
);
|
||||
if (items.isEmpty) break;
|
||||
for (final item in items) {
|
||||
fetchedMap[item.id] = item;
|
||||
}
|
||||
if (items.length < (isAuthorized ? 320 : 75) ||
|
||||
fetchedMap.length >= pool.postIds.length) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
} catch (_) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final ordered = <Post>[];
|
||||
for (final id in pool.postIds) {
|
||||
if (fetchedMap.containsKey(id)) {
|
||||
ordered.add(fetchedMap[id]!);
|
||||
}
|
||||
}
|
||||
return ordered.isNotEmpty ? ordered : fetchedMap.values.toList();
|
||||
}
|
||||
|
||||
Future<bool> votePost(String postId, int score) async {
|
||||
await _throttle();
|
||||
try {
|
||||
|
||||
@@ -48,6 +48,18 @@ final postProviderInstanceProvider =
|
||||
return ref.watch(providerManagerProvider).getProviderInstance(providerId);
|
||||
});
|
||||
|
||||
/// Fetches up to 6 recent posts from the same artist.
|
||||
final artistPostsProvider =
|
||||
FutureProvider.family<List<Post>, String>((ref, artistTag) async {
|
||||
if (artistTag.isEmpty) return const [];
|
||||
final result = await ref.watch(feedServiceProvider).refresh(
|
||||
tags: [artistTag],
|
||||
limit: 7,
|
||||
);
|
||||
if (result is! Success<List<Post>>) return const [];
|
||||
return (result as Success<List<Post>>).data.take(6).toList(growable: false);
|
||||
});
|
||||
|
||||
class PostDetailsScreen extends ConsumerWidget {
|
||||
const PostDetailsScreen({
|
||||
required this.providerId,
|
||||
@@ -377,6 +389,7 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
fileSizeBytes: fileSizeBytes,
|
||||
e621Provider: e621Provider,
|
||||
),
|
||||
_ArtistPostsCard(post: post),
|
||||
if (post.hasRelations) ...[
|
||||
const SizedBox(height: 16),
|
||||
PostRelationsCard(
|
||||
@@ -390,6 +403,9 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
PostPoolsCard(
|
||||
post: post,
|
||||
provider: e621Provider,
|
||||
onOpenComic: (poolId, currentPost, [targetId]) =>
|
||||
_openComic(context, e621Provider, poolId,
|
||||
currentPost, targetId),
|
||||
onOpenPostId: (targetId) =>
|
||||
_openPostById(context, post.providerId, targetId),
|
||||
),
|
||||
@@ -635,6 +651,7 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
fileSizeBytes: fileSizeBytes,
|
||||
e621Provider: e621Provider,
|
||||
),
|
||||
_ArtistPostsCard(post: post),
|
||||
if (post.hasRelations) ...[
|
||||
const SizedBox(height: 12),
|
||||
PostRelationsCard(
|
||||
@@ -648,6 +665,8 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
PostPoolsCard(
|
||||
post: post,
|
||||
provider: e621Provider,
|
||||
onOpenComic: (poolId, currentPost, [targetId]) =>
|
||||
_openComic(context, e621Provider, poolId, currentPost, targetId),
|
||||
onOpenPostId: (targetId) =>
|
||||
_openPostById(context, post.providerId, targetId),
|
||||
),
|
||||
@@ -885,6 +904,64 @@ class PostDetailsScreen extends ConsumerWidget {
|
||||
ref.invalidate(favoritesControllerProvider);
|
||||
}
|
||||
|
||||
Future<void> _openComic(
|
||||
BuildContext context,
|
||||
E621Provider provider,
|
||||
String poolId,
|
||||
Post currentPost, [
|
||||
String? targetId,
|
||||
]) async {
|
||||
final isRu = Localizations.maybeLocaleOf(context)?.languageCode == 'ru';
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(isRu ? 'Загрузка комикса...' : 'Loading comic...'),
|
||||
],
|
||||
),
|
||||
duration: const Duration(seconds: 4),
|
||||
),
|
||||
);
|
||||
|
||||
final poolPosts = await provider.getPoolPosts(poolId);
|
||||
if (!context.mounted) return;
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
|
||||
if (poolPosts.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(isRu
|
||||
? 'Не удалось загрузить страницы комикса'
|
||||
: 'Could not load comic pages'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final idToFind = targetId ?? currentPost.id;
|
||||
final targetIndex = poolPosts.indexWhere((p) => p.id == idToFind);
|
||||
final startPost =
|
||||
targetIndex >= 0 ? poolPosts[targetIndex] : poolPosts.first;
|
||||
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (ctx) => PostDetailsScreen(
|
||||
providerId: provider.id,
|
||||
postId: startPost.id,
|
||||
initialPost: startPost,
|
||||
postsList: poolPosts,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openPostById(
|
||||
BuildContext context, String providerId, String targetId) {
|
||||
Navigator.of(context).push(
|
||||
@@ -1053,10 +1130,12 @@ class _MobilePostPager extends StatefulWidget {
|
||||
class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
late final PageController _controller;
|
||||
bool _mediaGestureLocked = false;
|
||||
late int _currentPage = widget.initialIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentPage = widget.initialIndex;
|
||||
_controller = PageController(initialPage: widget.initialIndex);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_prefetchAround(widget.initialIndex);
|
||||
@@ -1066,10 +1145,10 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
@override
|
||||
void didUpdateWidget(covariant _MobilePostPager oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
// Исправленная проверка: сравниваем старый и новый initialIndex, а не текущий свайп
|
||||
if (oldWidget.initialIndex != widget.initialIndex &&
|
||||
widget.initialIndex >= 0 &&
|
||||
widget.initialIndex < widget.posts.length) {
|
||||
_currentPage = widget.initialIndex;
|
||||
if (_controller.hasClients) {
|
||||
_controller.jumpToPage(widget.initialIndex);
|
||||
}
|
||||
@@ -1087,7 +1166,7 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ScrollConfiguration(
|
||||
final pager = ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(
|
||||
dragDevices: {
|
||||
PointerDeviceKind.touch,
|
||||
@@ -1103,6 +1182,7 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
: const PageScrollPhysics(),
|
||||
itemCount: widget.posts.length,
|
||||
onPageChanged: (index) {
|
||||
setState(() => _currentPage = index);
|
||||
_prefetchAround(index);
|
||||
},
|
||||
itemBuilder: (context, index) {
|
||||
@@ -1135,7 +1215,63 @@ class _MobilePostPagerState extends State<_MobilePostPager> {
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.posts.length <= 1) return pager;
|
||||
|
||||
// Show the page indicator overlay only for comic/pool lists.
|
||||
final isComic = widget.posts.first.hasPools;
|
||||
if (!isComic) return pager;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
pager,
|
||||
Positioned(
|
||||
top: MediaQuery.paddingOf(context).top + 10,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Center(
|
||||
child: IgnorePointer(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 12, sigmaY: 12),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.60),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.18),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.auto_stories_rounded,
|
||||
size: 14, color: Colors.white),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'Комикс • Стр. ${_currentPage + 1} из ${widget.posts.length}',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: -0.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _setMediaGestureLocked(bool locked) {
|
||||
if (_mediaGestureLocked == locked) return;
|
||||
@@ -1273,6 +1409,163 @@ class _DownloadIntent extends Intent {
|
||||
const _DownloadIntent();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Artist posts card
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class _ArtistPostsCard extends ConsumerWidget {
|
||||
const _ArtistPostsCard({required this.post});
|
||||
|
||||
final Post post;
|
||||
|
||||
String _artistTag(Post post) {
|
||||
final artists = post.tagGroups['artist'] ??
|
||||
post.tags
|
||||
.where((t) => t.startsWith('artist:'))
|
||||
.map((t) => t.replaceFirst('artist:', ''))
|
||||
.toList();
|
||||
if (artists.isEmpty) return '';
|
||||
return 'artist:${artists.first}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final tag = _artistTag(post);
|
||||
if (tag.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
final artistPosts = ref.watch(artistPostsProvider(tag));
|
||||
final isRu = Localizations.maybeLocaleOf(context)?.languageCode == 'ru';
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
final theme = Theme.of(context);
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
final artistName = tag.replaceFirst('artist:', '');
|
||||
|
||||
return artistPosts.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
error: (_, __) => const SizedBox.shrink(),
|
||||
data: (posts) {
|
||||
if (posts.isEmpty) return const SizedBox.shrink();
|
||||
// exclude the current post
|
||||
final filtered =
|
||||
posts.where((p) => p.cacheKey != post.cacheKey).take(6).toList();
|
||||
if (filtered.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDark
|
||||
? scheme.surfaceContainerHigh.withValues(alpha: 0.70)
|
||||
: scheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isDark
|
||||
? Colors.white.withValues(alpha: 0.12)
|
||||
: scheme.outlineVariant.withValues(alpha: 0.40),
|
||||
width: 1.1,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.palette_rounded,
|
||||
size: 18, color: scheme.primary),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
isRu
|
||||
? 'Ещё от $artistName'
|
||||
: 'More by $artistName',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
onTap: () => context.push(
|
||||
'/search?q=${Uri.encodeComponent(tag)}',
|
||||
),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
isRu ? 'Все' : 'See all',
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: scheme.primary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 3),
|
||||
Icon(Icons.arrow_forward_ios_rounded,
|
||||
size: 11, color: scheme.primary),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SizedBox(
|
||||
height: 110,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: filtered.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final p = filtered[index];
|
||||
return InkWell(
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => PostDetailsScreen(
|
||||
providerId: p.providerId,
|
||||
postId: p.id,
|
||||
initialPost: p,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: p.previewUrl.isNotEmpty
|
||||
? p.previewUrl
|
||||
: p.sampleUrl,
|
||||
width: 110,
|
||||
height: 110,
|
||||
fit: BoxFit.cover,
|
||||
memCacheWidth: 220,
|
||||
memCacheHeight: 220,
|
||||
errorWidget: (_, __, ___) => Container(
|
||||
width: 110,
|
||||
height: 110,
|
||||
color: scheme.surfaceContainerHigh,
|
||||
child: Icon(Icons.broken_image_rounded,
|
||||
color: scheme.onSurfaceVariant),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CommentsSection extends ConsumerStatefulWidget {
|
||||
const _CommentsSection({required this.post});
|
||||
|
||||
|
||||
@@ -280,7 +280,36 @@ class _E621PoolSheetState extends State<E621PoolSheet> {
|
||||
),
|
||||
],
|
||||
|
||||
const SizedBox(height: 18),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Big "Read Full Comic" Button
|
||||
FilledButton.icon(
|
||||
onPressed: () {
|
||||
HapticFeedback.mediumImpact();
|
||||
Navigator.of(context).pop();
|
||||
final startId = pool.postIds.contains(widget.currentPost.id)
|
||||
? widget.currentPost.id
|
||||
: pool.postIds.first;
|
||||
widget.onSelectPostId(startId);
|
||||
},
|
||||
icon: const Icon(Icons.auto_stories_rounded, size: 18),
|
||||
label: Text(
|
||||
isRu
|
||||
? 'Читать весь комикс (${pool.postCount} стр.)'
|
||||
: 'Read Full Comic (${pool.postCount} pages)',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF0055AA),
|
||||
foregroundColor: Colors.white,
|
||||
minimumSize: const Size(double.infinity, 46),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Quick Page Navigator Banner
|
||||
Container(
|
||||
|
||||
@@ -9,12 +9,15 @@ class PostPoolsCard extends StatelessWidget {
|
||||
const PostPoolsCard({
|
||||
required this.post,
|
||||
required this.provider,
|
||||
required this.onOpenComic,
|
||||
required this.onOpenPostId,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final Post post;
|
||||
final E621Provider provider;
|
||||
final void Function(String poolId, Post currentPost, [String? targetId])
|
||||
onOpenComic;
|
||||
final ValueChanged<String> onOpenPostId;
|
||||
|
||||
@override
|
||||
@@ -83,62 +86,83 @@ class PostPoolsCard extends StatelessWidget {
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final poolId in pools)
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
E621PoolSheet.show(
|
||||
context,
|
||||
currentPost: post,
|
||||
poolId: poolId,
|
||||
provider: provider,
|
||||
onSelectPostId: onOpenPostId,
|
||||
);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF0055AA), Color(0xFF0077EE)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
HapticFeedback.lightImpact();
|
||||
onOpenComic(poolId, post);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color:
|
||||
const Color(0xFF0055AA).withValues(alpha: 0.25),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.auto_stories_rounded,
|
||||
size: 15, color: Colors.white),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
isRu
|
||||
? 'Открыть комикс (Пул #$poolId)'
|
||||
: 'Open Pool #$poolId',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14, vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
colors: [Color(0xFF0055AA), Color(0xFF0077EE)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF0055AA)
|
||||
.withValues(alpha: 0.25),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.arrow_forward_ios_rounded,
|
||||
size: 10, color: Colors.white),
|
||||
],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.auto_stories_rounded,
|
||||
size: 16, color: Colors.white),
|
||||
const SizedBox(width: 7),
|
||||
Text(
|
||||
isRu
|
||||
? 'Читать весь комикс (#$poolId)'
|
||||
: 'Read Full Comic (#$poolId)',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const Icon(Icons.arrow_forward_rounded,
|
||||
size: 13, color: Colors.white),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton.filledTonal(
|
||||
tooltip: isRu ? 'Все страницы' : 'All pages',
|
||||
style: IconButton.styleFrom(
|
||||
visualDensity: VisualDensity.compact,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
HapticFeedback.lightImpact();
|
||||
E621PoolSheet.show(
|
||||
context,
|
||||
currentPost: post,
|
||||
poolId: poolId,
|
||||
provider: provider,
|
||||
onSelectPostId: (targetId) =>
|
||||
onOpenComic(poolId, post, targetId),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.grid_view_rounded, size: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -580,7 +580,7 @@ class _DesktopShellState extends State<_DesktopShell>
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'v3.7.1',
|
||||
'v3.7.2',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: gel_rule_app
|
||||
description: Local backend/core layer for a Flutter booru browser app.
|
||||
publish_to: "none"
|
||||
version: 3.7.1+89
|
||||
version: 3.7.3+91
|
||||
|
||||
environment:
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
Reference in New Issue
Block a user