diff --git a/README.md b/README.md index be4473e..395cbfc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- Version 2.0.11 + Version 2.0.12 Flutter Platforms Gitea diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart index 3946d4f..a1c2764 100644 --- a/lib/app/app_version.dart +++ b/lib/app/app_version.dart @@ -1,3 +1,3 @@ -const appDisplayVersion = '2.0.11'; -const appBuildNumber = 33; +const appDisplayVersion = '2.0.12'; +const appBuildNumber = 34; const appReleaseChannel = 'Stable'; diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart index 3b6acbf..1fffae3 100644 --- a/lib/app/changelog.dart +++ b/lib/app/changelog.dart @@ -1,4 +1,17 @@ const lunarisChangelog = [ + LunarisChange( + version: '2.0.12', + title: 'Feed 2.0: Pinterest Modern Redesign & Sleek Cards', + bullets: [ + 'Feed 2.0 Design: modern squircle post cards with 14dp border radius, subtle micro-borders, and depth shadows.', + 'Unified Media Badges: sleek glassmorphism capsules for booru providers, ratings, video/GIF indicators, and status badges.', + 'Cinematic Bottom Gradient: gentle shading on post cards for clean presentation without obscuring art.', + 'Tactile Floating Favorite Button: circular glassmorphism heart button with neon glow and instant feedback.', + 'Compact 2-Tier Feed Toolbar: 22dp search capsule, filter count badge, and single unified horizontal scroll for sources and time periods.', + 'Quick Filter & Actions: direct access to random post, selection mode, rating picker, and active filter resets.', + 'Polished Search Presets: stylish bookmark capsules for quick access to saved search queries.', + ], + ), LunarisChange( version: '2.0.11', title: 'Mobile Video Player Redesign & Format Recognition Fix', diff --git a/lib/features/feed/presentation/feed_screen.dart b/lib/features/feed/presentation/feed_screen.dart index ec5dd22..ee75261 100644 --- a/lib/features/feed/presentation/feed_screen.dart +++ b/lib/features/feed/presentation/feed_screen.dart @@ -802,15 +802,22 @@ class _SearchPresetsBar extends StatelessWidget { ); }, child: FilterChip( + avatar: Icon( + active ? Icons.bookmark_rounded : Icons.bookmark_border_rounded, + size: 15, + color: active ? scheme.primary : scheme.onSurfaceVariant, + ), label: Text(name), selected: active, onSelected: (_) => onApply(raw), - selectedColor: scheme.primaryContainer, + selectedColor: scheme.primaryContainer.withValues(alpha: 0.8), checkmarkColor: scheme.onPrimaryContainer, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), labelStyle: TextStyle( color: active ? scheme.onPrimaryContainer : scheme.onSurface, - fontWeight: - active ? FontWeight.w600 : FontWeight.normal, + fontWeight: active ? FontWeight.w700 : FontWeight.w500, ), ), ); diff --git a/lib/features/feed/presentation/widgets/feed_toolbar.dart b/lib/features/feed/presentation/widgets/feed_toolbar.dart index f43deb3..99f3350 100644 --- a/lib/features/feed/presentation/widgets/feed_toolbar.dart +++ b/lib/features/feed/presentation/widgets/feed_toolbar.dart @@ -77,6 +77,8 @@ class _FeedToolbarState extends State { } Widget _buildDesktop(BuildContext context) { + final hasActiveFilters = widget.selectedProviderIds.isNotEmpty || widget.rating != null; + return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: Column( @@ -100,34 +102,42 @@ class _FeedToolbarState extends State { ), const SizedBox(width: 8), IconButton.filledTonal( - tooltip: widget.selectionMode ? 'Exit select' : 'Select posts', + tooltip: widget.selectionMode ? 'Выйти из выбора' : 'Выбрать посты', onPressed: widget.onToggleSelectionMode, icon: Icon(widget.selectionMode ? Icons.check_box_rounded : Icons.check_box_outline_blank_rounded), ), - IconButton.filledTonal( - tooltip: 'Providers', - onPressed: widget.onProviderFilter, - icon: const Icon(Icons.hub_rounded), + const SizedBox(width: 4), + Badge( + isLabelVisible: hasActiveFilters, + child: IconButton.filledTonal( + tooltip: 'Провайдеры', + onPressed: widget.onProviderFilter, + icon: const Icon(Icons.hub_rounded), + ), ), + const SizedBox(width: 4), IconButton.filledTonal( - tooltip: 'Rating', + tooltip: 'Рейтинг', onPressed: widget.onRatingFilter, icon: const Icon(Icons.tune_rounded), ), + const SizedBox(width: 4), IconButton( - tooltip: 'Clear filters', + tooltip: 'Очистить фильтры', onPressed: widget.onClearFilters, icon: const Icon(Icons.filter_alt_off_rounded), ), + const SizedBox(width: 4), IconButton( - tooltip: 'Refresh', + tooltip: 'Обновить', onPressed: widget.onRefresh, icon: const Icon(Icons.refresh_rounded), ), + const SizedBox(width: 4), IconButton.filledTonal( - tooltip: 'Random post', + tooltip: 'Случайный пост', onPressed: widget.onRandom, icon: const Icon(Icons.casino_rounded), ), @@ -154,7 +164,7 @@ class _FeedToolbarState extends State { if (widget.providers.isNotEmpty) ...[ FilterChip( selected: widget.selectedProviderIds.isEmpty, - label: const Text('All providers'), + label: const Text('Все источники'), onSelected: (_) => widget.onQuickProviderToggle('__all__'), ), const SizedBox(width: 8), @@ -162,7 +172,7 @@ class _FeedToolbarState extends State { FilterChip( selected: widget.selectedProviderIds.contains(provider.id), - avatar: const Icon(Icons.hub_rounded, size: 16), + avatar: const Icon(Icons.hub_rounded, size: 15), label: Text(provider.name), onSelected: (_) => widget.onQuickProviderToggle(provider.id), @@ -188,25 +198,62 @@ class _FeedToolbarState extends State { } Widget _buildMobile(BuildContext context) { + final hasActiveFilters = widget.selectedProviderIds.isNotEmpty || widget.rating != null; + return Padding( - padding: const EdgeInsets.fromLTRB(8, 6, 8, 6), + padding: const EdgeInsets.fromLTRB(10, 8, 10, 6), child: Column( children: [ - TagInputSearchBar( - initialValue: _query, - suggestions: widget.tagSuggestions, - onChanged: (value) { - _query = value; - widget.onSearchChanged(value); - }, - onSubmitted: widget.onSearch, - onSuggestionApplied: (value) { - _query = value; - widget.onSuggestionTap(value); - }, + Row( + children: [ + Expanded( + child: TagInputSearchBar( + initialValue: _query, + suggestions: widget.tagSuggestions, + onChanged: (value) { + _query = value; + widget.onSearchChanged(value); + }, + onSubmitted: widget.onSearch, + onSuggestionApplied: (value) { + _query = value; + widget.onSuggestionTap(value); + }, + ), + ), + const SizedBox(width: 6), + Badge( + isLabelVisible: hasActiveFilters, + child: IconButton.filledTonal( + visualDensity: VisualDensity.compact, + tooltip: 'Фильтры', + onPressed: widget.onProviderFilter, + icon: const Icon(Icons.tune_rounded, size: 20), + ), + ), + const SizedBox(width: 4), + IconButton.filledTonal( + visualDensity: VisualDensity.compact, + tooltip: 'Случайный пост', + onPressed: widget.onRandom, + icon: const Icon(Icons.casino_rounded, size: 20), + ), + const SizedBox(width: 4), + IconButton.filledTonal( + visualDensity: VisualDensity.compact, + tooltip: widget.selectionMode ? 'Выйти из выбора' : 'Выбрать посты', + onPressed: widget.onToggleSelectionMode, + icon: Icon( + widget.selectionMode + ? Icons.check_box_rounded + : Icons.check_box_outline_blank_rounded, + size: 20, + ), + ), + ], ), - const SizedBox(height: 8), if (widget.providerStatusMessage != null) ...[ + const SizedBox(height: 6), Align( alignment: Alignment.centerLeft, child: InputChip( @@ -214,55 +261,50 @@ class _FeedToolbarState extends State { label: Text(widget.providerStatusMessage!), ), ), - const SizedBox(height: 6), ], + const SizedBox(height: 7), _HorizontalWheelScroller( - height: 40, + height: 38, builder: (controller) => ListView( controller: controller, scrollDirection: Axis.horizontal, primary: false, children: [ - IconButton.filledTonal( - tooltip: - widget.selectionMode ? 'Exit select' : 'Select posts', - onPressed: widget.onToggleSelectionMode, - icon: Icon(widget.selectionMode - ? Icons.check_box_rounded - : Icons.check_box_outline_blank_rounded), - ), - const SizedBox(width: 6), - IconButton.filledTonal( - tooltip: 'Rating', - onPressed: widget.onRatingFilter, - icon: const Icon(Icons.tune_rounded), - ), - const SizedBox(width: 6), - IconButton( - tooltip: 'Clear filters', - onPressed: widget.onClearFilters, - icon: const Icon(Icons.filter_alt_off_rounded), - ), - IconButton( - tooltip: 'Refresh', + if (hasActiveFilters || widget.selectedTags.isNotEmpty) ...[ + ActionChip( + visualDensity: VisualDensity.compact, + avatar: const Icon(Icons.filter_alt_off_rounded, size: 15), + label: const Text('Сбросить'), + onPressed: widget.onClearFilters, + ), + const SizedBox(width: 6), + ], + ActionChip( + visualDensity: VisualDensity.compact, + avatar: const Icon(Icons.refresh_rounded, size: 15), + label: const Text('Обновить'), onPressed: widget.onRefresh, - icon: const Icon(Icons.refresh_rounded), ), - IconButton.filledTonal( - tooltip: 'Random post', - onPressed: widget.onRandom, - icon: const Icon(Icons.casino_rounded), + const SizedBox(width: 6), + FilterChip( + visualDensity: VisualDensity.compact, + selected: widget.rating != null, + avatar: const Icon(Icons.shield_outlined, size: 15), + label: Text(widget.rating ?? 'Рейтинг'), + onSelected: (_) => widget.onRatingFilter(), ), - const SizedBox(width: 8), + const SizedBox(width: 6), if (widget.providers.isNotEmpty) ...[ FilterChip( + visualDensity: VisualDensity.compact, selected: widget.selectedProviderIds.isEmpty, - label: const Text('All'), + label: const Text('Все'), onSelected: (_) => widget.onQuickProviderToggle('__all__'), ), const SizedBox(width: 6), for (final provider in widget.providers) ...[ FilterChip( + visualDensity: VisualDensity.compact, selected: widget.selectedProviderIds.contains(provider.id), label: Text(provider.name), @@ -272,19 +314,9 @@ class _FeedToolbarState extends State { const SizedBox(width: 6), ], ], - ], - ), - ), - const SizedBox(height: 6), - _HorizontalWheelScroller( - height: 36, - builder: (controller) => ListView( - controller: controller, - scrollDirection: Axis.horizontal, - primary: false, - children: [ for (final period in TopPeriodFilter.values) ...[ ChoiceChip( + visualDensity: VisualDensity.compact, selected: widget.topPeriodFilter == period, label: Text(period.label), onSelected: (_) => widget.onTopPeriodChanged(period), diff --git a/lib/shared/widgets/app_search_bar.dart b/lib/shared/widgets/app_search_bar.dart index ed08437..073979a 100644 --- a/lib/shared/widgets/app_search_bar.dart +++ b/lib/shared/widgets/app_search_bar.dart @@ -121,15 +121,16 @@ class _TagInputSearchBarState extends State { decoration: BoxDecoration( color: Theme.of(context).inputDecorationTheme.fillColor ?? scheme.surfaceContainerHighest.withValues(alpha: 0.38), - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(22), border: Border.all( color: _focusNode.hasFocus - ? scheme.primary.withValues(alpha: 0.45) - : Colors.transparent, + ? scheme.primary.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.08), + width: 1.0, ), ), child: InkWell( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(22), onTap: _focusNode.requestFocus, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7), diff --git a/lib/shared/widgets/post_card.dart b/lib/shared/widgets/post_card.dart index 21f8bcb..999a4ff 100644 --- a/lib/shared/widgets/post_card.dart +++ b/lib/shared/widgets/post_card.dart @@ -177,114 +177,159 @@ class _PostCardState extends ConsumerState ], ); }, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Stack( - children: [ - AspectRatio( - aspectRatio: aspect.clamp(0.28, 2.2), - child: BlurContent( - enabled: widget.blurExplicit && sensitive, - child: LayoutBuilder( - builder: (context, constraints) { - final dpr = MediaQuery.devicePixelRatioOf(context); - final cacheWidth = - (constraints.maxWidth * dpr).round().clamp(320, 1800); - final headers = _headersFor(post); - final videoUrls = MediaUrlSelector.video(post); - if (!mobile && - _hovered && - _resolvedPost != null && - MediaUrlSelector.isVideo(post) && - videoUrls.isNotEmpty) { - return _FeedVideoPreview( - videoUrl: videoUrls.first, - headers: headers, - fallbackImageUrl: imageUrl, - cacheWidth: cacheWidth, - ); - } - return CachedNetworkImage( - imageUrl: imageUrl, - httpHeaders: headers, - memCacheWidth: cacheWidth, - maxWidthDiskCache: cacheWidth, - fit: BoxFit.cover, - placeholder: (context, url) => const LoadingSkeleton(), - errorWidget: (context, url, error) => ColoredBox( - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, - child: const Center( - child: Icon(Icons.broken_image_rounded), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(14), + border: Border.all( + color: Colors.white.withValues(alpha: 0.09), + width: 0.8, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.18), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(13.2), + child: Stack( + children: [ + AspectRatio( + aspectRatio: aspect.clamp(0.28, 2.2), + child: BlurContent( + enabled: widget.blurExplicit && sensitive, + child: LayoutBuilder( + builder: (context, constraints) { + final dpr = MediaQuery.devicePixelRatioOf(context); + final cacheWidth = + (constraints.maxWidth * dpr).round().clamp(320, 1800); + final headers = _headersFor(post); + final videoUrls = MediaUrlSelector.video(post); + if (!mobile && + _hovered && + _resolvedPost != null && + MediaUrlSelector.isVideo(post) && + videoUrls.isNotEmpty) { + return _FeedVideoPreview( + videoUrl: videoUrls.first, + headers: headers, + fallbackImageUrl: imageUrl, + cacheWidth: cacheWidth, + ); + } + return CachedNetworkImage( + imageUrl: imageUrl, + httpHeaders: headers, + memCacheWidth: cacheWidth, + maxWidthDiskCache: cacheWidth, + fit: BoxFit.cover, + placeholder: (context, url) => const LoadingSkeleton(), + errorWidget: (context, url, error) => ColoredBox( + color: Theme.of(context) + .colorScheme + .surfaceContainerHighest, + child: const Center( + child: Icon(Icons.broken_image_rounded), + ), ), - ), - ); - }, + ); + }, + ), ), ), - ), - if (widget.showBadges) ...[ + // Bottom cinematic gradient overlay for badges and favorite Positioned( - left: 8, - top: 8, - child: _ProviderBadge(name: post.providerName), + left: 0, + right: 0, + bottom: 0, + height: 48, + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.black.withValues(alpha: 0.65), + Colors.transparent, + ], + ), + ), + ), + ), ), - Positioned( - right: 8, - top: 8, - child: RatingBadge(rating: post.rating), - ), - Positioned( - left: 8, - bottom: 8, - child: _MediaBadge(fileType: post.fileType), - ), - if (widget.isViewed) - const Positioned( + if (widget.showBadges) ...[ + Positioned( + left: 8, + top: 8, + child: _ProviderBadge(name: post.providerName), + ), + Positioned( + right: 8, + top: 8, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (post.fileType.toLowerCase().contains('video') || + post.fileType.toLowerCase().contains('webm') || + post.fileType.toLowerCase().contains('mp4') || + post.fileType.toLowerCase().contains('gif')) ...[ + _MediaBadge(fileType: post.fileType), + const SizedBox(width: 4), + ], + RatingBadge(rating: post.rating), + ], + ), + ), + Positioned( + left: 8, + bottom: 8, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.isViewed) ...[ + const _SeenBadge(), + const SizedBox(width: 4), + ], + if (widget.isDownloaded) const _DownloadedBadge(), + ], + ), + ), + ], + if (widget.selected) + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.24), + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 3, + ), + borderRadius: BorderRadius.circular(13), + ), + child: const Align( + alignment: Alignment.topRight, + child: Padding( + padding: EdgeInsets.all(8), + child: Icon(Icons.check_circle_rounded), + ), + ), + ), + ), + if (mobile) + Positioned( right: 8, bottom: 8, - child: _SeenBadge(), - ), - if (widget.isDownloaded) - Positioned( - right: widget.isViewed ? 72 : 8, - bottom: 8, - child: const _DownloadedBadge(), - ), - ], - if (widget.selected) - Positioned.fill( - child: DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .primary - .withValues(alpha: 0.24), - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 3, - ), - borderRadius: BorderRadius.circular(8), - ), - child: const Align( - alignment: Alignment.topRight, - child: Padding( - padding: EdgeInsets.all(8), - child: Icon(Icons.check_circle_rounded), - ), + child: _FavoriteButton( + isFavorite: widget.isFavorite, + onPressed: widget.onFavorite, ), ), - ), - if (mobile) - Positioned( - right: 8, - bottom: 8, - child: _FavoriteButton( - isFavorite: widget.isFavorite, - onPressed: widget.onFavorite, - ), - ), // Double-tap heart animation overlay if (_showHeart) Positioned.fill( @@ -369,7 +414,8 @@ class _PostCardState extends ConsumerState ), ), ), - ], + ], + ), ), ), ), @@ -635,13 +681,37 @@ class _FavoriteButton extends StatelessWidget { @override Widget build(BuildContext context) { - return IconButton.filledTonal( - visualDensity: VisualDensity.compact, - tooltip: isFavorite ? 'Remove favorite' : 'Favorite', - onPressed: onPressed, - icon: Icon( - isFavorite ? Icons.favorite_rounded : Icons.favorite_border_rounded, - size: 18, + return InkResponse( + onTap: onPressed, + radius: 20, + child: AnimatedContainer( + duration: AppMotion.duration(context, 150), + width: 34, + height: 34, + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.58), + shape: BoxShape.circle, + border: Border.all( + color: isFavorite + ? Colors.redAccent.withValues(alpha: 0.6) + : Colors.white.withValues(alpha: 0.16), + width: 0.9, + ), + boxShadow: [ + if (isFavorite) + BoxShadow( + color: Colors.redAccent.withValues(alpha: 0.35), + blurRadius: 10, + ), + ], + ), + child: Center( + child: Icon( + isFavorite ? Icons.favorite_rounded : Icons.favorite_border_rounded, + color: isFavorite ? Colors.redAccent : Colors.white, + size: 18, + ), + ), ), ); } @@ -654,16 +724,23 @@ class _ProviderBadge extends StatelessWidget { @override Widget build(BuildContext context) { - return DecoratedBox( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3.5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.55), - borderRadius: BorderRadius.circular(6), + color: Colors.black.withValues(alpha: 0.62), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.12), + width: 0.7, + ), ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - child: Text( - name, - style: const TextStyle(fontSize: 11, color: Colors.white), + child: Text( + name, + style: const TextStyle( + fontSize: 10.5, + color: Colors.white, + fontWeight: FontWeight.w700, + letterSpacing: 0.2, ), ), ); @@ -678,25 +755,32 @@ class _MediaBadge extends StatelessWidget { @override Widget build(BuildContext context) { final type = _type(fileType); - return DecoratedBox( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3.5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.55), - borderRadius: BorderRadius.circular(6), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(_icon(type), size: 12, color: Colors.white), - const SizedBox(width: 4), - Text( - type, - style: const TextStyle(fontSize: 11, color: Colors.white), - ), - ], + color: Colors.black.withValues(alpha: 0.62), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.12), + width: 0.7, ), ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(_icon(type), size: 13, color: Colors.white), + const SizedBox(width: 3.5), + Text( + type, + style: const TextStyle( + fontSize: 10.5, + color: Colors.white, + fontWeight: FontWeight.w800, + letterSpacing: 0.3, + ), + ), + ], + ), ); } @@ -723,25 +807,31 @@ class _SeenBadge extends StatelessWidget { @override Widget build(BuildContext context) { - return DecoratedBox( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3.5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.55), - borderRadius: BorderRadius.circular(6), - ), - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 7, vertical: 3), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.visibility_rounded, size: 12, color: Colors.white), - SizedBox(width: 4), - Text( - 'seen', - style: TextStyle(fontSize: 11, color: Colors.white), - ), - ], + color: Colors.black.withValues(alpha: 0.62), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.12), + width: 0.7, ), ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.visibility_rounded, size: 12, color: Colors.white70), + SizedBox(width: 3.5), + Text( + 'seen', + style: TextStyle( + fontSize: 10, + color: Colors.white70, + fontWeight: FontWeight.w700, + ), + ), + ], + ), ); } } @@ -751,14 +841,20 @@ class _DownloadedBadge extends StatelessWidget { @override Widget build(BuildContext context) { - return DecoratedBox( + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3.5), decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.55), - borderRadius: BorderRadius.circular(6), + color: Colors.black.withValues(alpha: 0.62), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.white.withValues(alpha: 0.12), + width: 0.7, + ), ), - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 7, vertical: 3), - child: Icon(Icons.download_done_rounded, size: 13, color: Colors.white), + child: const Icon( + Icons.download_done_rounded, + size: 13, + color: Colors.white, ), ); } diff --git a/lib/shared/widgets/post_masonry_grid.dart b/lib/shared/widgets/post_masonry_grid.dart index 67af38f..81ff957 100644 --- a/lib/shared/widgets/post_masonry_grid.dart +++ b/lib/shared/widgets/post_masonry_grid.dart @@ -95,7 +95,7 @@ class PostMasonryGrid extends StatelessWidget { itemBuilder: (context, index) { if (index >= posts.length) return const LoadingSkeleton(); return ClipRRect( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(14), child: SizedBox.expand( child: FittedBox( fit: BoxFit.cover, diff --git a/pubspec.yaml b/pubspec.yaml index 520e6b7..d389e34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: gel_rule_app description: Local backend/core layer for a Flutter booru browser app. publish_to: "none" -version: 2.0.11+33 +version: 2.0.12+34 environment: sdk: ">=3.3.0 <4.0.0"