diff --git a/README.md b/README.md index 34260fa..4f66745 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- Version 3.1.3 + Version 3.1.4 Flutter Platforms Wiki diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart index 2bc1374..18a0d70 100644 --- a/lib/app/app_version.dart +++ b/lib/app/app_version.dart @@ -1,3 +1,3 @@ -const appDisplayVersion = '3.1.3'; -const appBuildNumber = 64; +const appDisplayVersion = '3.1.4'; +const appBuildNumber = 65; const appReleaseChannel = 'Stable'; diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart index 47d77aa..3a7b8b4 100644 --- a/lib/app/changelog.dart +++ b/lib/app/changelog.dart @@ -1,4 +1,13 @@ const lunarisChangelog = [ + LunarisChange( + version: '3.1.4', + title: 'Оптимизация видеоплеера — адаптивная высота и идеальное прилегание полосы воспроизведения', + bullets: [ + 'Устранение зазора под видео: контейнер плеера на мобильных устройствах и ПК теперь динамически подстраивается под пропорции видео (aspect ratio) вместо жестко фиксированной высоты 62% экрана, устраняя огромные пустые черные полосы сверху и снизу.', + 'Точное позиционирование полосы воспроизведения: исправлен отступ снизу и ложное наложение системных insets на неполноэкранный плеер в карточке поста — ползунок воспроизведения, таймкоды и кнопка фуллскрина теперь аккуратно прилегают к нижнему краю видео без зависания в воздухе.', + 'Компактные элементы управления: выровнены буферный трек и слайдер прокрутки, оптимизирован градиент затенения для максимальной видимости и удобства управления.', + ], + ), LunarisChange( version: '3.1.3', title: 'iPhone Liquid Glass — Источники, Проверка сети и Навигация назад по вкладкам', diff --git a/lib/features/post/presentation/post_details_screen.dart b/lib/features/post/presentation/post_details_screen.dart index 34fc22d..ff0565b 100644 --- a/lib/features/post/presentation/post_details_screen.dart +++ b/lib/features/post/presentation/post_details_screen.dart @@ -219,34 +219,71 @@ class PostDetailsScreen extends ConsumerWidget { ), Expanded( child: Center( - child: SizedBox( - width: double.infinity, - height: 760, - child: PostMediaViewer( - key: ValueKey(post.cacheKey), - post: post, - localFilePath: localMedia?.savedPath, - qualityMode: qualityMode, - mediaHeaders: ref - .watch(postMediaHeadersProvider(post)) - .value ?? - const {}, - initialPosition: Duration( - milliseconds: settings.videoPlaybackPositions[ - post.cacheKey] ?? - 0, - ), - initialLoop: settings.videoPlayerLoop, - initialMuted: settings.videoPlayerMuted, - initialCoverVideo: settings.videoPlayerCover, - initialHalfVolume: - settings.videoPlayerHalfVolume, - onPlaybackSnapshot: (snapshot) => - _saveVideoSnapshot(ref, post, snapshot), - onPlaybackPreferencesChanged: (snapshot) => - _saveVideoPreferences(ref, snapshot), - ), - ), + child: MediaUrlSelector.isVideo(post) + ? ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 760, + ), + child: AspectRatio( + aspectRatio: + (post.width > 0 && post.height > 0) + ? (post.width / post.height) + .clamp(0.45, 2.4) + : (16 / 9), + child: PostMediaViewer( + key: ValueKey(post.cacheKey), + post: post, + localFilePath: localMedia?.savedPath, + qualityMode: qualityMode, + mediaHeaders: ref + .watch(postMediaHeadersProvider(post)) + .value ?? + const {}, + initialPosition: Duration( + milliseconds: settings.videoPlaybackPositions[ + post.cacheKey] ?? + 0, + ), + initialLoop: settings.videoPlayerLoop, + initialMuted: settings.videoPlayerMuted, + initialCoverVideo: settings.videoPlayerCover, + initialHalfVolume: + settings.videoPlayerHalfVolume, + onPlaybackSnapshot: (snapshot) => + _saveVideoSnapshot(ref, post, snapshot), + onPlaybackPreferencesChanged: (snapshot) => + _saveVideoPreferences(ref, snapshot), + ), + ), + ) + : SizedBox( + width: double.infinity, + height: 760, + child: PostMediaViewer( + key: ValueKey(post.cacheKey), + post: post, + localFilePath: localMedia?.savedPath, + qualityMode: qualityMode, + mediaHeaders: ref + .watch(postMediaHeadersProvider(post)) + .value ?? + const {}, + initialPosition: Duration( + milliseconds: settings.videoPlaybackPositions[ + post.cacheKey] ?? + 0, + ), + initialLoop: settings.videoPlayerLoop, + initialMuted: settings.videoPlayerMuted, + initialCoverVideo: settings.videoPlayerCover, + initialHalfVolume: + settings.videoPlayerHalfVolume, + onPlaybackSnapshot: (snapshot) => + _saveVideoSnapshot(ref, post, snapshot), + onPlaybackPreferencesChanged: (snapshot) => + _saveVideoPreferences(ref, snapshot), + ), + ), ), ), IconButton.filledTonal( @@ -409,33 +446,72 @@ class PostDetailsScreen extends ConsumerWidget { ? null : () => _showMobileQuickActions(context, ref, post, favoriteKeys), - child: SizedBox( - width: double.infinity, - height: MediaQuery.sizeOf(context).height * 0.62, - child: PostMediaViewer( - key: ValueKey(post.cacheKey), - post: post, - localFilePath: localMedia?.savedPath, - qualityMode: qualityMode, - mediaHeaders: - ref.watch(postMediaHeadersProvider(post)).value ?? const {}, - initialPosition: Duration( - milliseconds: - settings.videoPlaybackPositions[post.cacheKey] ?? 0, - ), - initialLoop: settings.videoPlayerLoop, - initialMuted: settings.videoPlayerMuted, - initialCoverVideo: settings.videoPlayerCover, - initialHalfVolume: settings.videoPlayerHalfVolume, - onPlaybackSnapshot: (snapshot) => - _saveVideoSnapshot(ref, post, snapshot), - onPlaybackPreferencesChanged: (snapshot) => - _saveVideoPreferences(ref, snapshot), - onMediaGestureLockChanged: (locked) { - onMediaGestureLockChanged?.call(locked); - }, - ), - ), + child: isVideo + ? Center( + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.70, + ), + child: AspectRatio( + aspectRatio: (post.width > 0 && post.height > 0) + ? (post.width / post.height).clamp(0.45, 2.4) + : (16 / 9), + child: PostMediaViewer( + key: ValueKey(post.cacheKey), + post: post, + localFilePath: localMedia?.savedPath, + qualityMode: qualityMode, + mediaHeaders: + ref.watch(postMediaHeadersProvider(post)).value ?? + const {}, + initialPosition: Duration( + milliseconds: + settings.videoPlaybackPositions[post.cacheKey] ?? + 0, + ), + initialLoop: settings.videoPlayerLoop, + initialMuted: settings.videoPlayerMuted, + initialCoverVideo: settings.videoPlayerCover, + initialHalfVolume: settings.videoPlayerHalfVolume, + onPlaybackSnapshot: (snapshot) => + _saveVideoSnapshot(ref, post, snapshot), + onPlaybackPreferencesChanged: (snapshot) => + _saveVideoPreferences(ref, snapshot), + onMediaGestureLockChanged: (locked) { + onMediaGestureLockChanged?.call(locked); + }, + ), + ), + ), + ) + : SizedBox( + width: double.infinity, + height: MediaQuery.sizeOf(context).height * 0.62, + child: PostMediaViewer( + key: ValueKey(post.cacheKey), + post: post, + localFilePath: localMedia?.savedPath, + qualityMode: qualityMode, + mediaHeaders: + ref.watch(postMediaHeadersProvider(post)).value ?? + const {}, + initialPosition: Duration( + milliseconds: + settings.videoPlaybackPositions[post.cacheKey] ?? 0, + ), + initialLoop: settings.videoPlayerLoop, + initialMuted: settings.videoPlayerMuted, + initialCoverVideo: settings.videoPlayerCover, + initialHalfVolume: settings.videoPlayerHalfVolume, + onPlaybackSnapshot: (snapshot) => + _saveVideoSnapshot(ref, post, snapshot), + onPlaybackPreferencesChanged: (snapshot) => + _saveVideoPreferences(ref, snapshot), + onMediaGestureLockChanged: (locked) { + onMediaGestureLockChanged?.call(locked); + }, + ), + ), ), const SizedBox(height: 12), PostActionBar( diff --git a/lib/features/post/presentation/widgets/post_media_viewer.dart b/lib/features/post/presentation/widgets/post_media_viewer.dart index a194dd7..6c1ef12 100644 --- a/lib/features/post/presentation/widgets/post_media_viewer.dart +++ b/lib/features/post/presentation/widgets/post_media_viewer.dart @@ -1792,7 +1792,10 @@ class _VideoControls extends StatelessWidget { ), ), child: SafeArea( + top: fullscreen, bottom: false, + left: fullscreen, + right: fullscreen, child: Row( children: [ if (fullscreen) ...[ @@ -1910,7 +1913,12 @@ class _VideoControls extends StatelessWidget { left: 0, right: 0, child: Container( - padding: const EdgeInsets.fromLTRB(14, 12, 14, 10), + padding: EdgeInsets.fromLTRB( + 14, + 8, + 14, + fullscreen ? 10 : 4, + ), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, @@ -1920,6 +1928,9 @@ class _VideoControls extends StatelessWidget { ), child: SafeArea( top: false, + bottom: fullscreen, + left: fullscreen, + right: fullscreen, child: StreamBuilder( stream: player.stream.duration, initialData: player.state.duration, @@ -1967,8 +1978,8 @@ class _VideoControls extends StatelessWidget { // Buffer progress track if (maxMs > 0 && bufferMs > 0) Positioned( - left: 20, - right: 20, + left: 14, + right: 14, child: ClipRRect( borderRadius: BorderRadius.circular(2), @@ -1976,10 +1987,12 @@ class _VideoControls extends StatelessWidget { value: (bufferMs / maxMs) .clamp(0.0, 1.0), minHeight: 3.5, - backgroundColor: Colors.transparent, + backgroundColor: + Colors.transparent, valueColor: AlwaysStoppedAnimation( - Colors.white.withValues(alpha: 0.28), + Colors.white + .withValues(alpha: 0.28), ), ), ), @@ -1997,6 +2010,10 @@ class _VideoControls extends StatelessWidget { const RoundSliderThumbShape( enabledThumbRadius: 6, ), + overlayShape: + const RoundSliderOverlayShape( + overlayRadius: 14, + ), ), child: Slider( value: valueMs, diff --git a/pubspec.yaml b/pubspec.yaml index 514b584..4f2ba92 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.3+64 +version: 3.1.4+65 environment: sdk: ">=3.3.0 <4.0.0"