refactor: clean up

This commit is contained in:
edde746
2025-11-22 06:33:49 +01:00
parent 38a447f57a
commit b455db4bdf
17 changed files with 82 additions and 411 deletions
-3
View File
@@ -7,8 +7,6 @@ import 'package:url_launcher/url_launcher.dart';
import 'screens/main_screen.dart';
import 'screens/auth_screen.dart';
import 'services/storage_service.dart';
import 'services/plex_auth_service.dart';
import 'services/server_connection_service.dart';
import 'services/macos_titlebar_service.dart';
import 'services/fullscreen_state_manager.dart';
import 'services/update_service.dart';
@@ -26,7 +24,6 @@ import 'services/data_aggregation_service.dart';
import 'services/server_registry.dart';
import 'utils/language_codes.dart';
import 'utils/app_logger.dart';
import 'utils/provider_extensions.dart';
import 'utils/orientation_helper.dart';
import 'i18n/strings.g.dart';
-29
View File
@@ -1,29 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'plex_server_info.g.dart';
@JsonSerializable()
class PlexServerInfo {
final String name;
final String? host;
final int? port;
final String? machineIdentifier;
final String version;
final bool? owned;
final bool? https;
PlexServerInfo({
required this.name,
this.host,
this.port,
this.machineIdentifier,
required this.version,
this.owned,
this.https,
});
factory PlexServerInfo.fromJson(Map<String, dynamic> json) =>
_$PlexServerInfoFromJson(json);
Map<String, dynamic> toJson() => _$PlexServerInfoToJson(this);
}
-29
View File
@@ -1,29 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'plex_server_info.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PlexServerInfo _$PlexServerInfoFromJson(Map<String, dynamic> json) =>
PlexServerInfo(
name: json['name'] as String,
host: json['host'] as String?,
port: (json['port'] as num?)?.toInt(),
machineIdentifier: json['machineIdentifier'] as String?,
version: json['version'] as String,
owned: json['owned'] as bool?,
https: json['https'] as bool?,
);
Map<String, dynamic> _$PlexServerInfoToJson(PlexServerInfo instance) =>
<String, dynamic>{
'name': instance.name,
'host': instance.host,
'port': instance.port,
'machineIdentifier': instance.machineIdentifier,
'version': instance.version,
'owned': instance.owned,
'https': instance.https,
};
-1
View File
@@ -7,7 +7,6 @@ import 'package:qr_flutter/qr_flutter.dart';
import '../services/plex_auth_service.dart';
import '../services/storage_service.dart';
import '../services/server_registry.dart';
import '../services/multi_server_manager.dart';
import '../providers/multi_server_provider.dart';
import '../providers/plex_client_provider.dart';
import '../i18n/strings.g.dart';
-4
View File
@@ -5,16 +5,12 @@ import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../models/plex_hub.dart';
import '../providers/plex_client_provider.dart';
import '../providers/multi_server_provider.dart';
import '../services/storage_service.dart';
import '../services/plex_auth_service.dart';
import '../widgets/media_card.dart';
import '../widgets/desktop_app_bar.dart';
import '../widgets/user_avatar_widget.dart';
import '../widgets/horizontal_scroll_with_arrows.dart';
import '../widgets/hub_section.dart';
import '../widgets/server_badge.dart';
import 'profile_switch_screen.dart';
import '../providers/user_profile_provider.dart';
import '../providers/settings_provider.dart';
-39
View File
@@ -1111,7 +1111,6 @@ class _LibraryManagementSheet extends StatefulWidget {
class _LibraryManagementSheetState extends State<_LibraryManagementSheet> {
late List<PlexLibrary> _tempLibraries;
List<String>? _serverOrder;
bool _isLoadingServerOrder = true;
@override
void initState() {
@@ -1128,7 +1127,6 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> {
if (mounted) {
setState(() {
_serverOrder = savedOrder;
_isLoadingServerOrder = false;
});
}
}
@@ -1441,43 +1439,6 @@ class _LibraryManagementSheetState extends State<_LibraryManagementSheet> {
);
}
/// Reorder libraries within a server group
void _reorderLibrariesInServer(
String serverKey,
List<PlexLibrary> serverLibraries,
int oldIndex,
int newIndex,
) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
// Get the library being moved
final library = serverLibraries[oldIndex];
// Find global indices
final globalOldIndex = _tempLibraries.indexOf(library);
// Calculate the new global index
// We need to find where this library should go in the global list
final targetLibrary = newIndex < serverLibraries.length
? serverLibraries[newIndex]
: serverLibraries.last;
final globalNewIndex = _tempLibraries.indexOf(targetLibrary);
// Reorder in global list
_tempLibraries.removeAt(globalOldIndex);
_tempLibraries.insert(
globalOldIndex < globalNewIndex ? globalNewIndex : globalNewIndex,
library,
);
});
// Apply immediately
widget.onReorder(_tempLibraries);
}
/// Build a single library tile
Widget _buildLibraryTile(
PlexLibrary library,
-4
View File
@@ -3,9 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../i18n/strings.g.dart';
import '../mixins/item_updatable.dart';
import '../mixins/refreshable.dart';
import '../models/plex_metadata.dart';
import '../providers/multi_server_provider.dart';
@@ -13,10 +11,8 @@ import '../providers/settings_provider.dart';
import '../services/settings_service.dart';
import '../utils/app_logger.dart';
import '../utils/grid_cross_axis_extent.dart';
import '../utils/provider_extensions.dart';
import '../widgets/desktop_app_bar.dart';
import '../widgets/media_card.dart';
import '../widgets/server_badge.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({super.key});
-2
View File
@@ -1,12 +1,10 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../utils/provider_extensions.dart';
import '../utils/video_player_navigation.dart';
import '../utils/duration_formatter.dart';
import '../utils/app_logger.dart';
import '../widgets/desktop_app_bar.dart';
import '../widgets/media_context_menu.dart';
import '../mixins/item_updatable.dart';
+5 -19
View File
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:media_kit/media_kit.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'settings_service.dart';
import '../utils/player_utils.dart';
class KeyboardShortcutsService {
static KeyboardShortcutsService? _instance;
@@ -230,21 +231,6 @@ class KeyboardShortcutsService {
return KeyEventResult.ignored;
}
/// Seeks by the given offset (can be positive or negative) while clamping
/// the result between 0 and the video duration
void _seekWithClamping(Player player, Duration offset) {
final currentPosition = player.state.position;
final duration = player.state.duration;
final newPosition = currentPosition + offset;
// Clamp between 0 and video duration
final clampedPosition = newPosition.isNegative
? Duration.zero
: (newPosition > duration ? duration : newPosition);
player.seek(clampedPosition);
}
void _executeAction(
String action,
Player player,
@@ -270,16 +256,16 @@ class KeyboardShortcutsService {
_settingsService.setVolume(newVolume);
break;
case 'seek_forward':
_seekWithClamping(player, Duration(seconds: _seekTimeSmall));
seekWithClamping(player, Duration(seconds: _seekTimeSmall));
break;
case 'seek_backward':
_seekWithClamping(player, Duration(seconds: -_seekTimeSmall));
seekWithClamping(player, Duration(seconds: -_seekTimeSmall));
break;
case 'seek_forward_large':
_seekWithClamping(player, Duration(seconds: _seekTimeLarge));
seekWithClamping(player, Duration(seconds: _seekTimeLarge));
break;
case 'seek_backward_large':
_seekWithClamping(player, Duration(seconds: -_seekTimeLarge));
seekWithClamping(player, Duration(seconds: -_seekTimeLarge));
break;
case 'fullscreen_toggle':
onToggleFullscreen?.call();
-3
View File
@@ -7,9 +7,6 @@ import 'storage_service.dart';
/// Centralized server configuration registry
/// Manages which servers are available, enabled/disabled, and their configurations
class ServerRegistry {
static const String _keyServersList = 'servers_list';
static const String _keyEnabledServers = 'enabled_servers';
final StorageService _storage;
ServerRegistry(this._storage);
+16
View File
@@ -0,0 +1,16 @@
import 'package:media_kit/media_kit.dart';
/// Seeks by the given offset (can be positive or negative) while clamping
/// the result between 0 and the video duration
void seekWithClamping(Player player, Duration offset) {
final currentPosition = player.state.position;
final duration = player.state.duration;
final newPosition = currentPosition + offset;
// Clamp between 0 and video duration
final clampedPosition = newPosition.isNegative
? Duration.zero
: (newPosition > duration ? duration : newPosition);
player.seek(clampedPosition);
}
+33
View File
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
/// Get the replay icon based on the duration
/// Returns numbered icons (replay_5, replay_10, replay_30) when available,
/// otherwise returns generic replay icon
IconData getReplayIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.replay_5;
case 10:
return Icons.replay_10;
case 30:
return Icons.replay_30;
default:
return Icons.replay; // Generic icon for custom durations
}
}
/// Get the forward icon based on the duration
/// Returns numbered icons (forward_5, forward_10, forward_30) when available,
/// otherwise returns generic forward icon
IconData getForwardIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.forward_5;
case 10:
return Icons.forward_10;
case 30:
return Icons.forward_30;
default:
return Icons.forward; // Generic icon for custom durations
}
}
-2
View File
@@ -4,8 +4,6 @@ import 'package:provider/provider.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
import '../models/plex_playlist.dart';
import '../providers/plex_client_provider.dart';
import '../providers/multi_server_provider.dart';
import '../providers/playback_state_provider.dart';
import '../utils/provider_extensions.dart';
import '../utils/app_logger.dart';
-1
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../client/plex_client.dart';
import '../models/plex_metadata.dart';
@@ -10,7 +10,7 @@ import '../../utils/desktop_window_padding.dart';
import '../../utils/duration_formatter.dart';
import '../../i18n/strings.g.dart';
import '../app_bar_back_button.dart';
import 'painters/chapter_marker_painter.dart';
import 'widgets/timeline_slider.dart';
/// Desktop-specific video controls layout with top bar and bottom controls
class DesktopVideoControls extends StatelessWidget {
@@ -188,9 +188,13 @@ class DesktopVideoControls extends StatelessWidget {
),
const SizedBox(width: 12),
Expanded(
child: _buildTimelineWithChapters(
child: TimelineSlider(
position: position,
duration: duration,
chapters: chapters,
chaptersLoaded: chaptersLoaded,
onSeek: onSeek,
onSeekEnd: onSeekEnd,
),
),
const SizedBox(width: 12),
@@ -313,88 +317,4 @@ class DesktopVideoControls extends StatelessWidget {
),
);
}
Widget _buildTimelineWithChapters({
required Duration position,
required Duration duration,
}) {
return Stack(
alignment: Alignment.center,
children: [
// Chapter markers layer
if (chaptersLoaded &&
chapters.isNotEmpty &&
duration.inMilliseconds > 0)
Positioned.fill(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
children:
chapters.map((chapter) {
final chapterPosition =
(chapter.startTimeOffset ?? 0) /
duration.inMilliseconds;
return Expanded(
flex: (chapterPosition * 1000).toInt(),
child: const SizedBox(),
);
}).toList()..add(
Expanded(
flex:
1000 -
chapters.fold<int>(
0,
(sum, chapter) =>
sum +
((chapter.startTimeOffset ?? 0) /
duration.inMilliseconds *
1000)
.toInt(),
),
child: const SizedBox(),
),
),
),
),
),
// Slider
Semantics(
label: t.videoControls.timelineSlider,
slider: true,
child: Slider(
value: duration.inMilliseconds > 0
? position.inMilliseconds.toDouble()
: 0.0,
min: 0.0,
max: duration.inMilliseconds.toDouble(),
onChanged: (value) {
onSeek(Duration(milliseconds: value.toInt()));
},
onChangeEnd: (value) {
onSeekEnd(Duration(milliseconds: value.toInt()));
},
activeColor: Colors.white,
inactiveColor: Colors.white.withValues(alpha: 0.3),
),
),
// Chapter marker indicators
if (chaptersLoaded &&
chapters.isNotEmpty &&
duration.inMilliseconds > 0)
Positioned.fill(
child: IgnorePointer(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: CustomPaint(
painter: ChapterMarkerPainter(
chapters: chapters,
duration: duration,
),
),
),
),
),
],
);
}
}
@@ -6,9 +6,11 @@ import 'package:media_kit/media_kit.dart';
import '../../models/plex_media_info.dart';
import '../../models/plex_metadata.dart';
import '../../utils/duration_formatter.dart';
import '../../utils/player_utils.dart';
import '../../utils/video_control_icons.dart';
import '../../i18n/strings.g.dart';
import '../app_bar_back_button.dart';
import 'painters/chapter_marker_painter.dart';
import 'widgets/timeline_slider.dart';
/// Mobile video controls layout for Plex video player
///
@@ -142,13 +144,13 @@ class MobileVideoControls extends StatelessWidget {
excludeSemantics: true,
child: IconButton(
icon: Icon(
_getReplayIcon(seekTimeSmall),
getReplayIcon(seekTimeSmall),
color: Colors.white,
size: 48,
),
iconSize: 48,
onPressed: () {
_seekWithClamping(Duration(seconds: -seekTimeSmall));
seekWithClamping(player, Duration(seconds: -seekTimeSmall));
},
),
),
@@ -198,13 +200,13 @@ class MobileVideoControls extends StatelessWidget {
excludeSemantics: true,
child: IconButton(
icon: Icon(
_getForwardIcon(seekTimeSmall),
getForwardIcon(seekTimeSmall),
color: Colors.white,
size: 48,
),
iconSize: 48,
onPressed: () {
_seekWithClamping(Duration(seconds: seekTimeSmall));
seekWithClamping(player, Duration(seconds: seekTimeSmall));
},
),
),
@@ -234,9 +236,13 @@ class MobileVideoControls extends StatelessWidget {
return Column(
children: [
_buildTimelineWithChapters(
TimelineSlider(
position: position,
duration: duration,
chapters: chapters,
chaptersLoaded: chaptersLoaded,
onSeek: onSeek,
onSeekEnd: onSeekEnd,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -270,90 +276,6 @@ class MobileVideoControls extends StatelessWidget {
);
}
Widget _buildTimelineWithChapters({
required Duration position,
required Duration duration,
}) {
return Stack(
alignment: Alignment.center,
children: [
// Chapter markers layer
if (chaptersLoaded &&
chapters.isNotEmpty &&
duration.inMilliseconds > 0)
Positioned.fill(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
children:
chapters.map((chapter) {
final chapterPosition =
(chapter.startTimeOffset ?? 0) /
duration.inMilliseconds;
return Expanded(
flex: (chapterPosition * 1000).toInt(),
child: const SizedBox(),
);
}).toList()..add(
Expanded(
flex:
1000 -
chapters.fold<int>(
0,
(sum, chapter) =>
sum +
((chapter.startTimeOffset ?? 0) /
duration.inMilliseconds *
1000)
.toInt(),
),
child: const SizedBox(),
),
),
),
),
),
// Slider
Semantics(
label: t.videoControls.timelineSlider,
slider: true,
child: Slider(
value: duration.inMilliseconds > 0
? position.inMilliseconds.toDouble()
: 0.0,
min: 0.0,
max: duration.inMilliseconds.toDouble(),
onChanged: (value) {
onSeek(Duration(milliseconds: value.toInt()));
},
onChangeEnd: (value) {
onSeekEnd(Duration(milliseconds: value.toInt()));
},
activeColor: Colors.white,
inactiveColor: Colors.white.withValues(alpha: 0.3),
),
),
// Chapter marker indicators
if (chaptersLoaded &&
chapters.isNotEmpty &&
duration.inMilliseconds > 0)
Positioned.fill(
child: IgnorePointer(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: CustomPaint(
painter: ChapterMarkerPainter(
chapters: chapters,
duration: duration,
),
),
),
),
),
],
);
}
/// Conditionally wraps child with SafeArea only in portrait mode
Widget _conditionalSafeArea({
required BuildContext context,
@@ -372,49 +294,4 @@ class MobileVideoControls extends StatelessWidget {
// In landscape, return child without SafeArea
return child;
}
void _seekWithClamping(Duration offset) {
final currentPosition = player.state.position;
final duration = player.state.duration;
final newPosition = currentPosition + offset;
// Clamp between 0 and video duration
final clampedPosition = newPosition.isNegative
? Duration.zero
: (newPosition > duration ? duration : newPosition);
player.seek(clampedPosition);
}
/// Get the replay icon based on the duration
/// Returns numbered icons (replay_5, replay_10, replay_30) when available,
/// otherwise returns generic replay icon
IconData _getReplayIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.replay_5;
case 10:
return Icons.replay_10;
case 30:
return Icons.replay_30;
default:
return Icons.replay; // Generic icon for custom durations
}
}
/// Get the forward icon based on the duration
/// Returns numbered icons (forward_5, forward_10, forward_30) when available,
/// otherwise returns generic forward icon
IconData _getForwardIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.forward_5;
case 10:
return Icons.forward_10;
case 30:
return Icons.forward_30;
default:
return Icons.forward; // Generic icon for custom durations
}
}
}
+10 -54
View File
@@ -15,7 +15,9 @@ import '../../screens/video_player_screen.dart';
import '../../services/keyboard_shortcuts_service.dart';
import '../../services/settings_service.dart';
import '../../utils/platform_detector.dart';
import '../../utils/player_utils.dart';
import '../../utils/provider_extensions.dart';
import '../../utils/video_control_icons.dart';
import '../../i18n/strings.g.dart';
import 'widgets/volume_control.dart';
import 'widgets/track_chapter_controls.dart';
@@ -399,7 +401,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
void _seekToPreviousChapter() {
if (_chapters.isEmpty) {
// No chapters - seek backward by configured amount
_seekWithClamping(Duration(seconds: -_seekTimeSmall));
seekWithClamping(widget.player, Duration(seconds: -_seekTimeSmall));
return;
}
@@ -422,7 +424,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
void _seekToNextChapter() {
if (_chapters.isEmpty) {
// No chapters - seek forward by configured amount
_seekWithClamping(Duration(seconds: _seekTimeSmall));
seekWithClamping(widget.player, Duration(seconds: _seekTimeSmall));
return;
}
@@ -438,21 +440,6 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
}
}
/// Seeks by the given offset (can be positive or negative) while clamping
/// the result between 0 and the video duration
void _seekWithClamping(Duration offset) {
final currentPosition = widget.player.state.position;
final duration = widget.player.state.duration;
final newPosition = currentPosition + offset;
// Clamp between 0 and video duration
final clampedPosition = newPosition.isNegative
? Duration.zero
: (newPosition > duration ? duration : newPosition);
widget.player.seek(clampedPosition);
}
/// Throttled seek for timeline slider - only sends seek events at most every 100ms
void _throttledSeek(Duration position) {
// Store the pending position
@@ -487,42 +474,11 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
_pendingSeekPosition = null;
}
/// Get the replay icon based on the duration
/// Returns numbered icons (replay_5, replay_10, replay_30) when available,
/// otherwise returns generic replay icon
IconData _getReplayIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.replay_5;
case 10:
return Icons.replay_10;
case 30:
return Icons.replay_30;
default:
return Icons.replay; // Generic icon for custom durations
}
}
/// Get the forward icon based on the duration
/// Returns numbered icons (forward_5, forward_10, forward_30) when available,
/// otherwise returns generic forward icon
IconData _getForwardIcon(int seconds) {
switch (seconds) {
case 5:
return Icons.forward_5;
case 10:
return Icons.forward_10;
case 30:
return Icons.forward_30;
default:
return Icons.forward; // Generic icon for custom durations
}
}
/// Handle double-tap skip forward or backward
void _handleDoubleTapSkip({required bool isForward}) {
// Perform the seek
_seekWithClamping(
seekWithClamping(
widget.player,
Duration(seconds: isForward ? _seekTimeSmall : -_seekTimeSmall),
);
@@ -573,8 +529,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
),
child: Icon(
_lastDoubleTapWasForward
? _getForwardIcon(_seekTimeSmall)
: _getReplayIcon(_seekTimeSmall),
? getForwardIcon(_seekTimeSmall)
: getReplayIcon(_seekTimeSmall),
color: Colors.white,
size: 48,
),
@@ -713,8 +669,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
onSeekToNextChapter: _seekToNextChapter,
onSeek: _throttledSeek,
onSeekEnd: _finalizeSeek,
getReplayIcon: _getReplayIcon,
getForwardIcon: _getForwardIcon,
getReplayIcon: getReplayIcon,
getForwardIcon: getForwardIcon,
),
),
),