Compare commits

...
2 Commits
9 changed files with 1694 additions and 664 deletions
+3 -1
View File
@@ -10,7 +10,7 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/Version-2.0.12-blueviolet?style=for-the-badge" alt="Version 2.0.12" />
<img src="https://img.shields.io/badge/Version-2.0.14-blueviolet?style=for-the-badge" alt="Version 2.0.14" />
<img src="https://img.shields.io/badge/Flutter-3.47+-02569B?style=for-the-badge&logo=flutter&logoColor=white" alt="Flutter" />
<img src="https://img.shields.io/badge/Platform-Android%20%7C%20Linux%20%7C%20Windows-brightgreen?style=for-the-badge" alt="Platforms" />
<img src="https://img.shields.io/badge/Gitea-RarDog%2FLunaris-orange?style=for-the-badge&logo=gitea&logoColor=white" alt="Gitea" />
@@ -20,6 +20,8 @@
## ✨ Основные возможности
- **⚙️ Settings 2.0**: Полный редизайн настроек с современными squircle-карточками, горизонтальной быстрой навигацией по разделам, мгновенными Segmented-кнопками выбора темы/языка/качества и стильной карточкой о приложении.
- **⚡ Мгновенные подсказки тегов (0 мс) и постоянный файловый кэш**: Локальный JSON-кэш на телефоне и ПК обеспечивает мгновенный отклик при наборе, а Danbooru, Gelbooru, Moebooru и e621 динамически дополняют базу тегов.
- **🎨 Цветные бейджи категорий и подсветка совпадений**: Выделение введенных букв жирным цветом акцента и четкая цветовая палитра категорий (художник — красный, персонаж — зеленый, копирайт — фиолетовый, вид — голубой).
- **📚 Расширенная история поиска и гибкие настройки**: До 2000 записей истории и до 20000 закэшированных тегов с ручным управлением и кнопками очистки в Настройках.
+2 -2
View File
@@ -1,3 +1,3 @@
const appDisplayVersion = '2.0.12';
const appBuildNumber = 34;
const appDisplayVersion = '2.0.14';
const appBuildNumber = 36;
const appReleaseChannel = 'Stable';
+22
View File
@@ -1,4 +1,26 @@
const lunarisChangelog = [
LunarisChange(
version: '2.0.14',
title: 'Settings 2.0: Modern Grouped Design & Quick Ergonomics',
bullets: [
'Settings 2.0 Layout: reimagined settings experience with modern squircle card groups, smooth borders, and curated accent badges.',
'Category Quick Navigation: horizontal capsule strip at the top to smoothly jump between General, Appearance, Feed, Filters, Storage, Diagnostics, and About.',
'Instant Segmented Controls: replaced tedious dropdowns with fast 1-tap SegmentedButtons for Theme (Dark/Light/Auto), Language (RU/EN), and Media Quality (Auto/Data saver/HQ).',
'Folder Structure Bottom Sheet: intuitive download directory template picker with preset chips and custom pattern editor.',
'Interactive Stepper & Limit Controls: modernized desktop & mobile column steppers and disk cache limits with accent pill badges.',
'Hero Brand Banner: polished app presentation card in About section with glowing icon, version build badge, update checker, and Gitea repository link.',
],
),
LunarisChange(
version: '2.0.13',
title: 'Toolbar Section Dividers & Post Card Badge Stacking Fix',
bullets: [
'Toolbar Section Dividers: added subtle vertical dividers between Actions (Reset/Refresh), Rating, Providers, and Top Periods to prevent visual clutter.',
'Post Card Badge Deconfliction: moved Rating badge (Explicit/Questionable/Safe) to the bottom-left beneath the Seen/Downloaded indicators.',
'Separated Provider & Media Badges: booru source (e.g. Gelbooru) remains top-left and Video/GIF badge remains top-right, preventing them from overlapping on narrow screens.',
'Expanded Bottom Gradient: increased bottom cinematic gradient height to 64dp for clean text and badge contrast.',
],
),
LunarisChange(
version: '2.0.12',
title: 'Feed 2.0: Pinterest Modern Redesign & Sleek Cards',
@@ -179,7 +179,9 @@ class _FeedToolbarState extends State<FeedToolbar> {
),
const SizedBox(width: 8),
],
const SizedBox(width: 8),
const SizedBox(width: 4),
const _ToolbarDivider(),
const SizedBox(width: 10),
],
for (final period in TopPeriodFilter.values) ...[
ChoiceChip(
@@ -286,6 +288,8 @@ class _FeedToolbarState extends State<FeedToolbar> {
onPressed: widget.onRefresh,
),
const SizedBox(width: 6),
const _ToolbarDivider(),
const SizedBox(width: 6),
FilterChip(
visualDensity: VisualDensity.compact,
selected: widget.rating != null,
@@ -294,6 +298,8 @@ class _FeedToolbarState extends State<FeedToolbar> {
onSelected: (_) => widget.onRatingFilter(),
),
const SizedBox(width: 6),
const _ToolbarDivider(),
const SizedBox(width: 6),
if (widget.providers.isNotEmpty) ...[
FilterChip(
visualDensity: VisualDensity.compact,
@@ -313,6 +319,9 @@ class _FeedToolbarState extends State<FeedToolbar> {
),
const SizedBox(width: 6),
],
const SizedBox(width: 2),
const _ToolbarDivider(),
const SizedBox(width: 6),
],
for (final period in TopPeriodFilter.values) ...[
ChoiceChip(
@@ -332,6 +341,20 @@ class _FeedToolbarState extends State<FeedToolbar> {
}
}
class _ToolbarDivider extends StatelessWidget {
const _ToolbarDivider();
@override
Widget build(BuildContext context) {
return Container(
width: 1,
height: 20,
margin: const EdgeInsets.symmetric(horizontal: 2, vertical: 8),
color: Theme.of(context).colorScheme.outlineVariant.withValues(alpha: 0.4),
);
}
}
class _HorizontalWheelScroller extends StatefulWidget {
const _HorizontalWheelScroller({
required this.builder,
File diff suppressed because it is too large Load Diff
+25 -23
View File
@@ -244,7 +244,7 @@ class _PostCardState extends ConsumerState<PostCard>
left: 0,
right: 0,
bottom: 0,
height: 48,
height: 64,
child: IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
@@ -252,7 +252,7 @@ class _PostCardState extends ConsumerState<PostCard>
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black.withValues(alpha: 0.65),
Colors.black.withValues(alpha: 0.72),
Colors.transparent,
],
),
@@ -266,34 +266,36 @@ class _PostCardState extends ConsumerState<PostCard>
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),
],
if (post.fileType.toLowerCase().contains('video') ||
post.fileType.toLowerCase().contains('webm') ||
post.fileType.toLowerCase().contains('mp4') ||
post.fileType.toLowerCase().contains('gif'))
Positioned(
right: 8,
top: 8,
child: _MediaBadge(fileType: post.fileType),
),
),
Positioned(
left: 8,
bottom: 8,
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isViewed) ...[
const _SeenBadge(),
const SizedBox(width: 4),
if (widget.isViewed || widget.isDownloaded) ...[
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isViewed) const _SeenBadge(),
if (widget.isViewed && widget.isDownloaded)
const SizedBox(width: 4),
if (widget.isDownloaded)
const _DownloadedBadge(),
],
),
const SizedBox(height: 4),
],
if (widget.isDownloaded) const _DownloadedBadge(),
RatingBadge(rating: post.rating),
],
),
),
+15 -8
View File
@@ -14,16 +14,23 @@ class RatingBadge extends StatelessWidget {
'explicit' || 'e' => Colors.red,
_ => Colors.blueGrey,
};
return DecoratedBox(
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6.5, vertical: 3),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(6),
color: color.withValues(alpha: 0.88),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.white.withValues(alpha: 0.18),
width: 0.7,
),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
child: Text(
rating.isEmpty ? 'unknown' : rating,
style: const TextStyle(fontSize: 11, color: Colors.white),
child: Text(
rating.isEmpty ? 'unknown' : rating,
style: const TextStyle(
fontSize: 10.5,
color: Colors.white,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
),
),
);
+1 -1
View File
@@ -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.12+34
version: 2.0.14+36
environment:
sdk: ">=3.3.0 <4.0.0"
+68
View File
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gel_rule_app/backend/backend.dart';
import 'package:gel_rule_app/features/settings/presentation/settings_controller.dart';
import 'package:gel_rule_app/features/settings/presentation/settings_screen.dart';
void main() {
testWidgets('SettingsScreen renders category quick nav and sections', (tester) async {
tester.view.physicalSize = const Size(1200, 2400);
tester.view.devicePixelRatio = 1.0;
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
final settings = AppSettings.defaults.copyWith(
languageCode: 'ru',
themeMode: 'dark',
);
await tester.pumpWidget(
ProviderScope(
overrides: [
settingsControllerProvider.overrideWith(
() => _FakeSettingsController(settings),
),
],
child: const MaterialApp(
home: SettingsScreen(),
),
),
);
await tester.pumpAndSettle();
// Verify Category Quick Nav renders
expect(find.text('Основное'), findsWidgets);
expect(find.text('Внешний вид'), findsWidgets);
expect(find.text('Лента и раскладка'), findsWidgets);
expect(find.text('Фильтры'), findsWidgets);
expect(find.text('Хранилище'), findsWidgets);
// Verify Theme mode segment buttons
expect(find.text('Темная'), findsOneWidget);
expect(find.text('Светлая'), findsOneWidget);
// Both ThemeMode and MediaQualityMode have 'Авто'
expect(find.text('Авто'), findsNWidgets(2));
// Verify Language segmented buttons
expect(find.text('🇷🇺 RU'), findsOneWidget);
expect(find.text('🇬🇧 EN'), findsOneWidget);
});
}
class _FakeSettingsController extends SettingsController {
_FakeSettingsController(this._initial);
final AppSettings _initial;
@override
Future<AppSettings> build() async => _initial;
@override
Future<void> saveSettings(AppSettings settings) async {
state = AsyncData(settings);
}
}