diff --git a/README.md b/README.md
index 4f66745..66eb9b1 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 18a0d70..29348d0 100644
--- a/lib/app/app_version.dart
+++ b/lib/app/app_version.dart
@@ -1,3 +1,3 @@
-const appDisplayVersion = '3.1.4';
-const appBuildNumber = 65;
+const appDisplayVersion = '3.1.5';
+const appBuildNumber = 66;
const appReleaseChannel = 'Stable';
diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart
index 3a7b8b4..96ee15f 100644
--- a/lib/app/changelog.dart
+++ b/lib/app/changelog.dart
@@ -1,4 +1,15 @@
const lunarisChangelog = [
+ LunarisChange(
+ version: '3.1.5',
+ title: 'Сохранение громкости видео, исправление полноэкранного плеера, фильтрации и удобства комментариев',
+ bullets: [
+ 'Сохранение уровня громкости (Persistent Volume): выбранная громкость видеоплеера теперь сохраняется при свайп-регулировке и переключениях между видео и сохраняется даже после перезапуска приложения.',
+ 'Исправление кнопок в полноэкранном режиме: кнопки паузы/воспроизведения и быстрой перемотки на ±10 секунд в центре экрана теперь работают мгновенно и надежно с анимацией визуального отклика (Seek Ripple).',
+ 'Сохранение выбранных источников при очистке тегов: удаление всех тегов в строке поиска больше не сбрасывает выбранные провайдеры до значения «Все», сохраняя ваши активные источники.',
+ 'Плавная строка поиска: при выборе тега из подсказок поле ввода больше не сдвигается слишком далеко в сторону, оставляя тег и курсор в поле зрения.',
+ 'Удобный доступ к комментариям в посте: увеличен отступ снизу в деталях поста и расширена зона клика блока комментариев, позволяя комфортно прокрутить их выше системных жестов навигации.',
+ ],
+ ),
LunarisChange(
version: '3.1.4',
title: 'Оптимизация видеоплеера — адаптивная высота и идеальное прилегание полосы воспроизведения',
diff --git a/lib/backend/services/settings_service.dart b/lib/backend/services/settings_service.dart
index 7439728..ad8ed69 100644
--- a/lib/backend/services/settings_service.dart
+++ b/lib/backend/services/settings_service.dart
@@ -39,6 +39,7 @@ class AppSettings {
required this.motionRefreshMode,
required this.autoBatterySaver60Hz,
required this.videoPlayerMuted,
+ this.videoPlayerVolume = 100.0,
required this.videoPlayerHalfVolume,
required this.videoPlayerLoop,
required this.videoPlayerCover,
@@ -94,6 +95,7 @@ class AppSettings {
final String motionRefreshMode;
final bool autoBatterySaver60Hz;
final bool videoPlayerMuted;
+ final double videoPlayerVolume;
final bool videoPlayerHalfVolume;
final bool videoPlayerLoop;
final bool videoPlayerCover;
@@ -146,6 +148,7 @@ class AppSettings {
motionRefreshMode: 'auto',
autoBatterySaver60Hz: true,
videoPlayerMuted: false,
+ videoPlayerVolume: 100.0,
videoPlayerHalfVolume: false,
videoPlayerLoop: false,
videoPlayerCover: false,
@@ -191,6 +194,7 @@ class AppSettings {
String? motionRefreshMode,
bool? autoBatterySaver60Hz,
bool? videoPlayerMuted,
+ double? videoPlayerVolume,
bool? videoPlayerHalfVolume,
bool? videoPlayerLoop,
bool? videoPlayerCover,
@@ -252,6 +256,7 @@ class AppSettings {
motionRefreshMode: motionRefreshMode ?? this.motionRefreshMode,
autoBatterySaver60Hz: autoBatterySaver60Hz ?? this.autoBatterySaver60Hz,
videoPlayerMuted: videoPlayerMuted ?? this.videoPlayerMuted,
+ videoPlayerVolume: videoPlayerVolume ?? this.videoPlayerVolume,
videoPlayerHalfVolume:
videoPlayerHalfVolume ?? this.videoPlayerHalfVolume,
videoPlayerLoop: videoPlayerLoop ?? this.videoPlayerLoop,
@@ -309,6 +314,7 @@ class AppSettings {
'motionRefreshMode': motionRefreshMode,
'autoBatterySaver60Hz': autoBatterySaver60Hz,
'videoPlayerMuted': videoPlayerMuted,
+ 'videoPlayerVolume': videoPlayerVolume,
'videoPlayerHalfVolume': videoPlayerHalfVolume,
'videoPlayerLoop': videoPlayerLoop,
'videoPlayerCover': videoPlayerCover,
@@ -392,6 +398,8 @@ class AppSettings {
defaults.autoBatterySaver60Hz,
videoPlayerMuted:
(json['videoPlayerMuted'] as bool?) ?? defaults.videoPlayerMuted,
+ videoPlayerVolume: (json['videoPlayerVolume'] as num?)?.toDouble() ??
+ defaults.videoPlayerVolume,
videoPlayerHalfVolume: (json['videoPlayerHalfVolume'] as bool?) ??
defaults.videoPlayerHalfVolume,
videoPlayerLoop:
@@ -601,6 +609,17 @@ class SettingsService {
return updateSettings(settings.copyWith(videoPlaybackPositions: positions));
}
+ Future> setVideoPlayerVolume(double volume) async {
+ final result = await getSettings();
+ if (result is Error) return Error(result.failure);
+ final settings = (result as Success).data;
+ final clamped = volume.clamp(0.0, 100.0);
+ if ((settings.videoPlayerVolume - clamped).abs() < 0.01) {
+ return const Success(null);
+ }
+ return updateSettings(settings.copyWith(videoPlayerVolume: clamped));
+ }
+
Future> appendDiagnosticLog(String message) async {
final result = await getSettings();
if (result is Error) return Error(result.failure);
diff --git a/lib/features/feed/presentation/feed_screen.dart b/lib/features/feed/presentation/feed_screen.dart
index ee75261..c9363d3 100644
--- a/lib/features/feed/presentation/feed_screen.dart
+++ b/lib/features/feed/presentation/feed_screen.dart
@@ -374,7 +374,11 @@ class _FeedScreenState extends ConsumerState
void _submitSearch(String query) {
final trimmed = query.trim();
if (trimmed.isEmpty) {
- _clearFilters();
+ if (widget.initialQuery != null) {
+ context.go('/');
+ } else {
+ _applySearchQuery('');
+ }
return;
}
final location = '/?q=${Uri.encodeQueryComponent(trimmed)}';
diff --git a/lib/features/post/presentation/post_details_screen.dart b/lib/features/post/presentation/post_details_screen.dart
index ff0565b..90ff961 100644
--- a/lib/features/post/presentation/post_details_screen.dart
+++ b/lib/features/post/presentation/post_details_screen.dart
@@ -21,6 +21,7 @@ import '../../../shared/widgets/rating_badge.dart';
import '../../collections/presentation/collection_form_dialog.dart';
import '../../favorites/presentation/favorites_controller.dart';
import '../../feed/presentation/feed_controller.dart';
+import '../../settings/presentation/settings_controller.dart';
import '../../viewed/presentation/viewed_controller.dart';
import 'post_details_controller.dart';
import 'widgets/cloud_mirrors_card.dart';
@@ -249,6 +250,10 @@ class PostDetailsScreen extends ConsumerWidget {
initialCoverVideo: settings.videoPlayerCover,
initialHalfVolume:
settings.videoPlayerHalfVolume,
+ initialVolume: settings.videoPlayerVolume,
+ onVolumeChanged: (vol) => ref
+ .read(settingsControllerProvider.notifier)
+ .setVideoPlayerVolume(vol),
onPlaybackSnapshot: (snapshot) =>
_saveVideoSnapshot(ref, post, snapshot),
onPlaybackPreferencesChanged: (snapshot) =>
@@ -278,6 +283,10 @@ class PostDetailsScreen extends ConsumerWidget {
initialCoverVideo: settings.videoPlayerCover,
initialHalfVolume:
settings.videoPlayerHalfVolume,
+ initialVolume: settings.videoPlayerVolume,
+ onVolumeChanged: (vol) => ref
+ .read(settingsControllerProvider.notifier)
+ .setVideoPlayerVolume(vol),
onPlaybackSnapshot: (snapshot) =>
_saveVideoSnapshot(ref, post, snapshot),
onPlaybackPreferencesChanged: (snapshot) =>
@@ -392,6 +401,7 @@ class PostDetailsScreen extends ConsumerWidget {
),
const SizedBox(height: 16),
_CommentsSection(post: post),
+ const SizedBox(height: 80),
],
),
),
@@ -436,7 +446,12 @@ class PostDetailsScreen extends ConsumerWidget {
if ((details.primaryVelocity ?? 0) > 900) _close(context);
},
child: ListView(
- padding: const EdgeInsets.fromLTRB(8, 8, 8, 16),
+ padding: EdgeInsets.fromLTRB(
+ 8,
+ 8,
+ 8,
+ 120 + MediaQuery.paddingOf(context).bottom,
+ ),
children: [
GestureDetector(
behavior: HitTestBehavior.deferToChild,
@@ -473,6 +488,10 @@ class PostDetailsScreen extends ConsumerWidget {
initialMuted: settings.videoPlayerMuted,
initialCoverVideo: settings.videoPlayerCover,
initialHalfVolume: settings.videoPlayerHalfVolume,
+ initialVolume: settings.videoPlayerVolume,
+ onVolumeChanged: (vol) => ref
+ .read(settingsControllerProvider.notifier)
+ .setVideoPlayerVolume(vol),
onPlaybackSnapshot: (snapshot) =>
_saveVideoSnapshot(ref, post, snapshot),
onPlaybackPreferencesChanged: (snapshot) =>
@@ -503,6 +522,10 @@ class PostDetailsScreen extends ConsumerWidget {
initialMuted: settings.videoPlayerMuted,
initialCoverVideo: settings.videoPlayerCover,
initialHalfVolume: settings.videoPlayerHalfVolume,
+ initialVolume: settings.videoPlayerVolume,
+ onVolumeChanged: (vol) => ref
+ .read(settingsControllerProvider.notifier)
+ .setVideoPlayerVolume(vol),
onPlaybackSnapshot: (snapshot) =>
_saveVideoSnapshot(ref, post, snapshot),
onPlaybackPreferencesChanged: (snapshot) =>
@@ -609,6 +632,7 @@ class PostDetailsScreen extends ConsumerWidget {
),
const SizedBox(height: 12),
_CommentsSection(post: post),
+ const SizedBox(height: 24),
],
),
);
@@ -855,7 +879,8 @@ class PostDetailsScreen extends ConsumerWidget {
if (settings.videoPlayerMuted == snapshot.muted &&
settings.videoPlayerHalfVolume == snapshot.halfVolume &&
settings.videoPlayerLoop == snapshot.loopVideo &&
- settings.videoPlayerCover == snapshot.coverVideo) {
+ settings.videoPlayerCover == snapshot.coverVideo &&
+ (settings.videoPlayerVolume - snapshot.volume).abs() < 0.01) {
return;
}
await ref.read(settingsServiceProvider).updateSettings(
@@ -864,6 +889,7 @@ class PostDetailsScreen extends ConsumerWidget {
videoPlayerHalfVolume: snapshot.halfVolume,
videoPlayerLoop: snapshot.loopVideo,
videoPlayerCover: snapshot.coverVideo,
+ videoPlayerVolume: snapshot.volume,
),
);
ref.invalidate(appSettingsProvider);
@@ -1160,7 +1186,7 @@ class _CommentsSectionState extends ConsumerState<_CommentsSection> {
),
clipBehavior: Clip.antiAlias,
child: ExpansionTile(
- tilePadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 2),
+ tilePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 14),
initiallyExpanded: false,
shape: const Border(),
diff --git a/lib/features/post/presentation/widgets/post_media_viewer.dart b/lib/features/post/presentation/widgets/post_media_viewer.dart
index 6c1ef12..f65f99d 100644
--- a/lib/features/post/presentation/widgets/post_media_viewer.dart
+++ b/lib/features/post/presentation/widgets/post_media_viewer.dart
@@ -27,10 +27,12 @@ class PostMediaViewer extends StatefulWidget {
this.initialMuted = false,
this.initialCoverVideo = false,
this.initialHalfVolume = false,
+ this.initialVolume = 100.0,
this.qualityMode = MediaQualityMode.auto,
this.mediaHeaders = const {},
this.onPlaybackSnapshot,
this.onPlaybackPreferencesChanged,
+ this.onVolumeChanged,
this.onMediaGestureLockChanged,
super.key,
});
@@ -44,10 +46,12 @@ class PostMediaViewer extends StatefulWidget {
final bool initialMuted;
final bool initialCoverVideo;
final bool initialHalfVolume;
+ final double initialVolume;
final MediaQualityMode qualityMode;
final Map mediaHeaders;
final ValueChanged? onPlaybackSnapshot;
final ValueChanged? onPlaybackPreferencesChanged;
+ final ValueChanged? onVolumeChanged;
final ValueChanged? onMediaGestureLockChanged;
@override
@@ -68,6 +72,7 @@ class _PostMediaViewerState extends State
late bool _muted;
late bool _loopVideo;
late bool _halfVolume;
+ late double _currentVolume;
String? _videoError;
bool _retriedFormatError = false;
Timer? _hideTimer;
@@ -83,6 +88,7 @@ class _PostMediaViewerState extends State
_muted = widget.initialMuted;
_loopVideo = widget.initialLoop;
_halfVolume = widget.initialHalfVolume;
+ _currentVolume = widget.initialVolume;
_imageUrls = _buildImageUrls(widget.post);
_videoUrls = _buildVideoUrls(widget.post);
if (_isVideo(widget.post) && _videoUrls.isNotEmpty) {
@@ -167,6 +173,11 @@ class _PostMediaViewerState extends State
muted: _muted,
loopVideo: _loopVideo,
halfVolume: _halfVolume,
+ initialVolume: _currentVolume,
+ onVolumeChanged: (vol) {
+ _currentVolume = vol;
+ widget.onVolumeChanged?.call(vol);
+ },
fullscreen: widget.fullscreen,
errorMessage: _videoError,
onInteract: _showControls,
@@ -534,6 +545,7 @@ class _PostMediaViewerState extends State
muted: _muted,
halfVolume: _halfVolume,
coverVideo: _coverVideo,
+ initialVolume: _currentVolume,
errorMessage: _videoError,
onRetry: _retryVideo,
onChanged: (snapshot) {
@@ -560,12 +572,14 @@ class _PostMediaViewerState extends State
_loopVideo = snapshot.loopVideo;
_coverVideo = snapshot.coverVideo;
_halfVolume = snapshot.halfVolume;
+ _currentVolume = snapshot.volume;
});
await _applyVolume();
await _player?.setPlaylistMode(
_loopVideo ? PlaylistMode.single : PlaylistMode.none,
);
_emitPlaybackPreferences();
+ widget.onVolumeChanged?.call(_currentVolume);
}
VideoPlaybackSnapshot _snapshot() {
@@ -577,15 +591,16 @@ class _PostMediaViewerState extends State
halfVolume: _halfVolume,
loopVideo: _loopVideo,
coverVideo: _coverVideo,
+ volume: _currentVolume,
);
}
Future _applyVolume() async {
await _player?.setVolume(_muted
- ? 0
+ ? 0.0
: _halfVolume
- ? 50
- : 100);
+ ? 50.0
+ : _currentVolume);
}
void _emitPlaybackPreferences() {
@@ -656,6 +671,7 @@ class VideoPlaybackSnapshot {
required this.halfVolume,
required this.loopVideo,
required this.coverVideo,
+ this.volume = 100.0,
});
final Duration position;
@@ -664,6 +680,7 @@ class VideoPlaybackSnapshot {
final bool halfVolume;
final bool loopVideo;
final bool coverVideo;
+ final double volume;
}
class _FullscreenVideoPage extends StatefulWidget {
@@ -675,6 +692,7 @@ class _FullscreenVideoPage extends StatefulWidget {
required this.muted,
required this.halfVolume,
required this.coverVideo,
+ this.initialVolume = 100.0,
required this.errorMessage,
required this.onRetry,
required this.onChanged,
@@ -687,6 +705,7 @@ class _FullscreenVideoPage extends StatefulWidget {
final bool muted;
final bool halfVolume;
final bool coverVideo;
+ final double initialVolume;
final String? errorMessage;
final VoidCallback onRetry;
final ValueChanged onChanged;
@@ -701,6 +720,7 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
late bool _muted;
late bool _loopVideo;
late bool _halfVolume;
+ late double _currentVolume;
late bool _isLandscape;
Timer? _hideTimer;
@@ -711,6 +731,7 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
_muted = widget.muted;
_loopVideo = widget.loopVideo;
_halfVolume = widget.halfVolume;
+ _currentVolume = widget.initialVolume;
_isLandscape = widget.aspectRatio > 1.05;
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
_applyOrientation();
@@ -790,6 +811,11 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
muted: _muted,
loopVideo: _loopVideo,
halfVolume: _halfVolume,
+ initialVolume: _currentVolume,
+ onVolumeChanged: (vol) {
+ _currentVolume = vol;
+ widget.onChanged(_snapshot());
+ },
fullscreen: true,
errorMessage: widget.errorMessage,
onInteract: _showControls,
@@ -883,10 +909,10 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
Future _applyVolume() async {
await widget.player.setVolume(_muted
- ? 0
+ ? 0.0
: _halfVolume
- ? 50
- : 100);
+ ? 50.0
+ : _currentVolume);
}
VideoPlaybackSnapshot _snapshot() {
@@ -897,6 +923,7 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
halfVolume: _halfVolume,
loopVideo: _loopVideo,
coverVideo: _coverVideo,
+ volume: _currentVolume,
);
}
}
@@ -915,6 +942,8 @@ class _VideoSurface extends StatefulWidget {
required this.muted,
required this.halfVolume,
required this.loopVideo,
+ this.initialVolume = 100.0,
+ this.onVolumeChanged,
required this.fullscreen,
required this.errorMessage,
required this.onInteract,
@@ -934,6 +963,8 @@ class _VideoSurface extends StatefulWidget {
final bool muted;
final bool halfVolume;
final bool loopVideo;
+ final double initialVolume;
+ final ValueChanged? onVolumeChanged;
final bool fullscreen;
final String? errorMessage;
final VoidCallback onInteract;
@@ -965,7 +996,9 @@ class _VideoSurfaceState extends State<_VideoSurface> {
@override
void initState() {
super.initState();
- _currentVolume = widget.player.state.volume;
+ _currentVolume = widget.player.state.volume > 0
+ ? widget.player.state.volume
+ : widget.initialVolume;
}
@override
@@ -983,26 +1016,37 @@ class _VideoSurfaceState extends State<_VideoSurface> {
if (target < Duration.zero) target = Duration.zero;
if (dur > Duration.zero && target > dur) target = dur;
widget.player.seek(target);
+ widget.onInteract();
+ if (delta.inSeconds < 0) {
+ setState(() => _showLeftSeek = true);
+ _seekLeftTimer?.cancel();
+ _seekLeftTimer = Timer(const Duration(milliseconds: 650), () {
+ if (mounted) setState(() => _showLeftSeek = false);
+ });
+ } else if (delta.inSeconds > 0) {
+ setState(() => _showRightSeek = true);
+ _seekRightTimer?.cancel();
+ _seekRightTimer = Timer(const Duration(milliseconds: 650), () {
+ if (mounted) setState(() => _showRightSeek = false);
+ });
+ }
}
void _onDoubleTapAt(Offset localPosition, double width) {
if (_isLocked) return;
if (localPosition.dx < width * 0.4) {
_seekBy(const Duration(seconds: -10));
- setState(() => _showLeftSeek = true);
- _seekLeftTimer?.cancel();
- _seekLeftTimer = Timer(const Duration(milliseconds: 650), () {
- if (mounted) setState(() => _showLeftSeek = false);
- });
} else if (localPosition.dx > width * 0.6) {
_seekBy(const Duration(seconds: 10));
- setState(() => _showRightSeek = true);
- _seekRightTimer?.cancel();
- _seekRightTimer = Timer(const Duration(milliseconds: 650), () {
- if (mounted) setState(() => _showRightSeek = false);
- });
} else {
- widget.player.playOrPause();
+ if (widget.player.state.playing) {
+ widget.player.pause();
+ } else {
+ if (widget.player.state.completed) {
+ widget.player.seek(Duration.zero);
+ }
+ widget.player.play();
+ }
widget.onInteract();
}
}
@@ -1025,6 +1069,7 @@ class _VideoSurfaceState extends State<_VideoSurface> {
final newVol = (_currentVolume - deltaY * 0.5).clamp(0.0, 100.0);
_currentVolume = newVol;
widget.player.setVolume(newVol);
+ widget.onVolumeChanged?.call(newVol);
setState(() => _showVolumeIndicator = true);
_volumeTimer?.cancel();
_volumeTimer = Timer(const Duration(milliseconds: 1100), () {
@@ -1094,6 +1139,7 @@ class _VideoSurfaceState extends State<_VideoSurface> {
onToggleLoop: widget.onToggleLoop,
onFullscreen: widget.onFullscreen,
onSeekBy: _seekBy,
+ onInteract: widget.onInteract,
),
),
),
@@ -1728,6 +1774,7 @@ class _VideoControls extends StatelessWidget {
required this.onToggleLoop,
required this.onFullscreen,
required this.onSeekBy,
+ this.onInteract,
});
final Player player;
@@ -1744,6 +1791,7 @@ class _VideoControls extends StatelessWidget {
final VoidCallback onToggleLoop;
final VoidCallback onFullscreen;
final void Function(Duration) onSeekBy;
+ final VoidCallback? onInteract;
@override
Widget build(BuildContext context) {
@@ -1864,7 +1912,10 @@ class _VideoControls extends StatelessWidget {
icon: Icons.replay_10_rounded,
size: 46,
iconSize: 26,
- onPressed: () => onSeekBy(const Duration(seconds: -10)),
+ onPressed: () {
+ onSeekBy(const Duration(seconds: -10));
+ onInteract?.call();
+ },
),
const SizedBox(width: 32),
Container(
@@ -1885,7 +1936,17 @@ class _VideoControls extends StatelessWidget {
tooltip: playing ? 'Пауза' : 'Воспроизведение',
iconSize: 42,
color: scheme.onPrimary,
- onPressed: player.playOrPause,
+ onPressed: () async {
+ if (player.state.playing) {
+ await player.pause();
+ } else {
+ if (player.state.completed) {
+ await player.seek(Duration.zero);
+ }
+ await player.play();
+ }
+ onInteract?.call();
+ },
icon: Icon(
playing
? Icons.pause_rounded
@@ -1899,7 +1960,10 @@ class _VideoControls extends StatelessWidget {
icon: Icons.forward_10_rounded,
size: 46,
iconSize: 26,
- onPressed: () => onSeekBy(const Duration(seconds: 10)),
+ onPressed: () {
+ onSeekBy(const Duration(seconds: 10));
+ onInteract?.call();
+ },
),
],
);
diff --git a/lib/features/settings/presentation/settings_controller.dart b/lib/features/settings/presentation/settings_controller.dart
index 9514318..20f541c 100644
--- a/lib/features/settings/presentation/settings_controller.dart
+++ b/lib/features/settings/presentation/settings_controller.dart
@@ -23,6 +23,16 @@ class SettingsController extends AsyncNotifier {
ref.invalidate(appSettingsProvider);
}
+ Future setVideoPlayerVolume(double volume) async {
+ final current = state.value ?? AppSettings.defaults;
+ final clamped = volume.clamp(0.0, 100.0);
+ if ((current.videoPlayerVolume - clamped).abs() < 0.01) return;
+ final updated = current.copyWith(videoPlayerVolume: clamped);
+ state = AsyncData(updated);
+ await ref.read(settingsServiceProvider).setVideoPlayerVolume(clamped);
+ ref.invalidate(appSettingsProvider);
+ }
+
Future clearCache() async {
await ref.read(cacheServiceProvider).clear();
}
diff --git a/lib/shared/widgets/app_search_bar.dart b/lib/shared/widgets/app_search_bar.dart
index 1625e00..6b27409 100644
--- a/lib/shared/widgets/app_search_bar.dart
+++ b/lib/shared/widgets/app_search_bar.dart
@@ -167,8 +167,10 @@ class _TagInputSearchBarState extends State {
void _scrollToEnd() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_tagScrollController.hasClients) {
+ final max = _tagScrollController.position.maxScrollExtent;
+ final target = (max - 40).clamp(0.0, max);
_tagScrollController.animateTo(
- _tagScrollController.position.maxScrollExtent,
+ target,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
@@ -286,8 +288,8 @@ class _TagInputSearchBarState extends State {
const SizedBox(width: 6),
],
ConstrainedBox(
- constraints: const BoxConstraints(
- minWidth: 120,
+ constraints: BoxConstraints(
+ minWidth: _tags.isEmpty ? 120 : 64,
maxWidth: 300,
),
child: Focus(
diff --git a/pubspec.yaml b/pubspec.yaml
index 4f2ba92..cf0055c 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: 3.1.4+65
+version: 3.1.5+66
environment:
sdk: ">=3.3.0 <4.0.0"