diff --git a/README.md b/README.md
index be4473e..395cbfc 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
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"