refactor: consolidate files
This commit is contained in:
@@ -40,7 +40,8 @@ platform :android do
|
||||
"../build/app/outputs/flutter-apk/app-arm64-v8a-release.apk"
|
||||
],
|
||||
overwrite_upload: true,
|
||||
overwrite_upload_mode: 'reuse'
|
||||
overwrite_upload_mode: 'reuse',
|
||||
changes_not_sent_for_review: true
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'screens/main_screen.dart';
|
||||
import 'screens/auth_screen.dart';
|
||||
import 'services/storage_service.dart';
|
||||
import 'services/macos_titlebar_service.dart';
|
||||
import 'services/macos_window_service.dart';
|
||||
import 'services/fullscreen_state_manager.dart';
|
||||
import 'services/settings_service.dart';
|
||||
import 'utils/platform_detector.dart';
|
||||
@@ -100,7 +100,7 @@ void main() async {
|
||||
}
|
||||
|
||||
// Configure macOS window with custom titlebar (depends on window manager)
|
||||
futures.add(MacOSTitlebarService.setupCustomTitlebar());
|
||||
futures.add(MacOSWindowService.setupCustomTitlebar());
|
||||
|
||||
// Initialize storage service
|
||||
futures.add(StorageService.getInstance());
|
||||
|
||||
@@ -1,4 +1,56 @@
|
||||
import 'remote_command_type.dart';
|
||||
enum RemoteCommandType {
|
||||
// Navigation
|
||||
dpadUp,
|
||||
dpadDown,
|
||||
dpadLeft,
|
||||
dpadRight,
|
||||
select,
|
||||
back,
|
||||
contextMenu,
|
||||
|
||||
// Playback
|
||||
play,
|
||||
pause,
|
||||
playPause,
|
||||
stop,
|
||||
seekForward,
|
||||
seekBackward,
|
||||
nextTrack,
|
||||
previousTrack,
|
||||
skipIntro,
|
||||
skipCredits,
|
||||
|
||||
// Volume
|
||||
volumeUp,
|
||||
volumeDown,
|
||||
volumeMute,
|
||||
volumeSet,
|
||||
|
||||
// Tab Navigation
|
||||
tabNext,
|
||||
tabPrevious,
|
||||
tabDiscover,
|
||||
tabLibraries,
|
||||
tabSearch,
|
||||
tabDownloads,
|
||||
tabSettings,
|
||||
|
||||
// Quick Actions
|
||||
home,
|
||||
search,
|
||||
subtitles,
|
||||
audioTracks,
|
||||
qualitySettings,
|
||||
fullscreen,
|
||||
|
||||
// Session Management
|
||||
ping,
|
||||
pong,
|
||||
deviceInfo,
|
||||
disconnect,
|
||||
ack,
|
||||
syncState,
|
||||
}
|
||||
|
||||
class RemoteCommand {
|
||||
final RemoteCommandType type;
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
enum RemoteCommandType {
|
||||
// Navigation
|
||||
dpadUp,
|
||||
dpadDown,
|
||||
dpadLeft,
|
||||
dpadRight,
|
||||
select,
|
||||
back,
|
||||
contextMenu,
|
||||
|
||||
// Playback
|
||||
play,
|
||||
pause,
|
||||
playPause,
|
||||
stop,
|
||||
seekForward,
|
||||
seekBackward,
|
||||
nextTrack,
|
||||
previousTrack,
|
||||
skipIntro,
|
||||
skipCredits,
|
||||
|
||||
// Volume
|
||||
volumeUp,
|
||||
volumeDown,
|
||||
volumeMute,
|
||||
volumeSet,
|
||||
|
||||
// Tab Navigation
|
||||
tabNext,
|
||||
tabPrevious,
|
||||
tabDiscover,
|
||||
tabLibraries,
|
||||
tabSearch,
|
||||
tabDownloads,
|
||||
tabSettings,
|
||||
|
||||
// Quick Actions
|
||||
home,
|
||||
search,
|
||||
subtitles,
|
||||
audioTracks,
|
||||
qualitySettings,
|
||||
fullscreen,
|
||||
|
||||
// Session Management
|
||||
ping,
|
||||
pong,
|
||||
deviceInfo,
|
||||
disconnect,
|
||||
ack,
|
||||
syncState,
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'trusted_device.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class TrustedDevice {
|
||||
final String peerId;
|
||||
final String deviceName;
|
||||
final String platform;
|
||||
final DateTime firstConnected;
|
||||
final DateTime lastConnected;
|
||||
final bool isApproved;
|
||||
|
||||
TrustedDevice({
|
||||
required this.peerId,
|
||||
required this.deviceName,
|
||||
required this.platform,
|
||||
DateTime? firstConnected,
|
||||
DateTime? lastConnected,
|
||||
this.isApproved = false,
|
||||
}) : firstConnected = firstConnected ?? DateTime.now(),
|
||||
lastConnected = lastConnected ?? DateTime.now();
|
||||
|
||||
factory TrustedDevice.fromJson(Map<String, dynamic> json) => _$TrustedDeviceFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$TrustedDeviceToJson(this);
|
||||
|
||||
TrustedDevice copyWith({
|
||||
String? peerId,
|
||||
String? deviceName,
|
||||
String? platform,
|
||||
DateTime? firstConnected,
|
||||
DateTime? lastConnected,
|
||||
bool? isApproved,
|
||||
}) {
|
||||
return TrustedDevice(
|
||||
peerId: peerId ?? this.peerId,
|
||||
deviceName: deviceName ?? this.deviceName,
|
||||
platform: platform ?? this.platform,
|
||||
firstConnected: firstConnected ?? this.firstConnected,
|
||||
lastConnected: lastConnected ?? this.lastConnected,
|
||||
isApproved: isApproved ?? this.isApproved,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is TrustedDevice && other.peerId == peerId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => peerId.hashCode;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'trusted_device.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
TrustedDevice _$TrustedDeviceFromJson(Map<String, dynamic> json) => TrustedDevice(
|
||||
peerId: json['peerId'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
platform: json['platform'] as String,
|
||||
firstConnected: json['firstConnected'] == null ? null : DateTime.parse(json['firstConnected'] as String),
|
||||
lastConnected: json['lastConnected'] == null ? null : DateTime.parse(json['lastConnected'] as String),
|
||||
isApproved: json['isApproved'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TrustedDeviceToJson(TrustedDevice instance) => <String, dynamic>{
|
||||
'peerId': instance.peerId,
|
||||
'deviceName': instance.deviceName,
|
||||
'platform': instance.platform,
|
||||
'firstConnected': instance.firstConnected.toIso8601String(),
|
||||
'lastConnected': instance.lastConnected.toIso8601String(),
|
||||
'isApproved': instance.isApproved,
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../models.dart';
|
||||
import 'player_android.dart';
|
||||
import 'platform/player_android.dart';
|
||||
import 'player_native.dart';
|
||||
import 'player_state.dart';
|
||||
import 'player_streams.dart';
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
import '../models/companion_remote/remote_command.dart';
|
||||
import '../models/companion_remote/remote_command_type.dart';
|
||||
import '../models/companion_remote/remote_session.dart';
|
||||
import '../models/companion_remote/trusted_device.dart';
|
||||
import '../services/companion_remote/companion_remote_peer_service.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
|
||||
typedef CommandReceivedCallback = void Function(RemoteCommand command);
|
||||
typedef DeviceApprovalCallback = Future<bool> Function(RemoteDevice device);
|
||||
|
||||
class CompanionRemoteProvider with ChangeNotifier {
|
||||
RemoteSession? _session;
|
||||
CompanionRemotePeerService? _peerService;
|
||||
String _deviceName = 'Unknown Device';
|
||||
String _platform = 'unknown';
|
||||
final List<TrustedDevice> _trustedDevices = [];
|
||||
bool _isPlayerActive = false;
|
||||
|
||||
static const String _storageKey = 'companion_remote_trusted_devices';
|
||||
static const String _lastDeviceKey = 'companion_remote_last_device';
|
||||
static const int _maxReconnectAttempts = 5;
|
||||
|
||||
Timer? _reconnectTimer;
|
||||
@@ -44,7 +36,6 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
StreamSubscription<RemoteSessionStatus>? _statusSubscription;
|
||||
|
||||
CommandReceivedCallback? onCommandReceived;
|
||||
DeviceApprovalCallback? onDeviceApprovalRequired;
|
||||
|
||||
bool get isInSession => _session != null && _session!.status != RemoteSessionStatus.disconnected;
|
||||
bool get isHost => _session?.isHost ?? false;
|
||||
@@ -55,12 +46,10 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
String? get sessionId => _session?.sessionId;
|
||||
String? get pin => _session?.pin;
|
||||
RemoteDevice? get connectedDevice => _session?.connectedDevice;
|
||||
List<TrustedDevice> get trustedDevices => List.unmodifiable(_trustedDevices);
|
||||
bool get isPlayerActive => _isPlayerActive;
|
||||
|
||||
CompanionRemoteProvider() {
|
||||
_initializeDeviceInfo();
|
||||
_loadTrustedDevices();
|
||||
}
|
||||
|
||||
Future<void> _initializeDeviceInfo() async {
|
||||
@@ -117,12 +106,10 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
},
|
||||
);
|
||||
|
||||
_deviceConnectedSubscription = _peerService!.onDeviceConnected.listen((device) async {
|
||||
_deviceConnectedSubscription = _peerService!.onDeviceConnected.listen((device) {
|
||||
appLogger.d('CompanionRemote: Device connected: ${device.name}');
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected, connectedDevice: device);
|
||||
notifyListeners();
|
||||
|
||||
await addTrustedDevice(device, requireApproval: isHost);
|
||||
});
|
||||
|
||||
_deviceDisconnectedSubscription = _peerService!.onDeviceDisconnected.listen((_) {
|
||||
@@ -375,92 +362,6 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _loadTrustedDevices() async {
|
||||
try {
|
||||
final storage = await StorageService.getInstance();
|
||||
final json = storage.prefs.getString(_storageKey);
|
||||
if (json != null) {
|
||||
final List<dynamic> list = jsonDecode(json);
|
||||
_trustedDevices.clear();
|
||||
_trustedDevices.addAll(list.map((e) => TrustedDevice.fromJson(e as Map<String, dynamic>)));
|
||||
appLogger.d('CompanionRemote: Loaded ${_trustedDevices.length} trusted devices');
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Failed to load trusted devices', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _saveTrustedDevices() async {
|
||||
try {
|
||||
final storage = await StorageService.getInstance();
|
||||
final json = jsonEncode(_trustedDevices.map((e) => e.toJson()).toList());
|
||||
await storage.prefs.setString(_storageKey, json);
|
||||
appLogger.d('CompanionRemote: Saved ${_trustedDevices.length} trusted devices');
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Failed to save trusted devices', error: e);
|
||||
}
|
||||
}
|
||||
|
||||
bool isDeviceTrusted(String peerId) {
|
||||
return _trustedDevices.any((d) => d.peerId == peerId && d.isApproved);
|
||||
}
|
||||
|
||||
Future<void> addTrustedDevice(RemoteDevice device, {bool requireApproval = true}) async {
|
||||
final existing = _trustedDevices.where((d) => d.peerId == device.id).firstOrNull;
|
||||
|
||||
if (existing != null) {
|
||||
final updated = existing.copyWith(
|
||||
deviceName: device.name,
|
||||
platform: device.platform,
|
||||
lastConnected: DateTime.now(),
|
||||
isApproved: !requireApproval || existing.isApproved,
|
||||
);
|
||||
_trustedDevices.remove(existing);
|
||||
_trustedDevices.add(updated);
|
||||
} else {
|
||||
bool approved = !requireApproval;
|
||||
|
||||
if (requireApproval && onDeviceApprovalRequired != null) {
|
||||
approved = await onDeviceApprovalRequired!(device);
|
||||
}
|
||||
|
||||
_trustedDevices.add(
|
||||
TrustedDevice(peerId: device.id, deviceName: device.name, platform: device.platform, isApproved: approved),
|
||||
);
|
||||
}
|
||||
|
||||
await _saveTrustedDevices();
|
||||
|
||||
if (isRemote) {
|
||||
final storage = await StorageService.getInstance();
|
||||
await storage.prefs.setString(_lastDeviceKey, device.id);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> removeTrustedDevice(String peerId) async {
|
||||
_trustedDevices.removeWhere((d) => d.peerId == peerId);
|
||||
await _saveTrustedDevices();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> approveTrustedDevice(String peerId) async {
|
||||
final device = _trustedDevices.where((d) => d.peerId == peerId).firstOrNull;
|
||||
if (device != null) {
|
||||
final updated = device.copyWith(isApproved: true);
|
||||
_trustedDevices.remove(device);
|
||||
_trustedDevices.add(updated);
|
||||
await _saveTrustedDevices();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getLastConnectedDevicePeerId() async {
|
||||
final storage = await StorageService.getInstance();
|
||||
return storage.prefs.getString(_lastDeviceKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_reconnectTimer?.cancel();
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../models/companion_remote/remote_command_type.dart';
|
||||
import '../../models/companion_remote/remote_command.dart';
|
||||
import '../../models/companion_remote/remote_session.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../providers/companion_remote_provider.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../models/plex_first_character.dart';
|
||||
import '../../models/plex_first_character.dart';
|
||||
import 'alpha_jump_helper.dart';
|
||||
|
||||
/// Vertical strip of letters for jumping through sorted library items.
|
||||
@@ -1,5 +1,5 @@
|
||||
import '../data/ducet_order.dart';
|
||||
import '../models/plex_first_character.dart';
|
||||
import '../../data/ducet_order.dart';
|
||||
import '../../models/plex_first_character.dart';
|
||||
|
||||
/// Shared letter-index mapping logic used by both [AlphaJumpBar] (desktop/tablet/TV)
|
||||
/// and [AlphaScrollHandle] (phone).
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../models/plex_first_character.dart';
|
||||
import '../../models/plex_first_character.dart';
|
||||
import 'alpha_jump_helper.dart';
|
||||
|
||||
/// Phone-optimized draggable scroll handle that appears on scroll and shows
|
||||
@@ -14,9 +14,9 @@ import '../../../providers/settings_provider.dart';
|
||||
import '../../../utils/error_message_utils.dart';
|
||||
import '../../../utils/grid_size_calculator.dart';
|
||||
import '../../../utils/layout_constants.dart';
|
||||
import '../../../widgets/alpha_jump_bar.dart';
|
||||
import '../../../widgets/alpha_jump_helper.dart';
|
||||
import '../../../widgets/alpha_scroll_handle.dart';
|
||||
import '../alpha_jump_bar.dart';
|
||||
import '../alpha_jump_helper.dart';
|
||||
import '../alpha_scroll_handle.dart';
|
||||
import '../../../widgets/focusable_media_card.dart';
|
||||
import '../../../widgets/media_card.dart';
|
||||
import '../../../widgets/focusable_filter_chip.dart';
|
||||
|
||||
@@ -22,7 +22,7 @@ import '../../providers/settings_provider.dart';
|
||||
import '../../providers/theme_provider.dart';
|
||||
import '../../providers/user_profile_provider.dart';
|
||||
import '../../services/keyboard_shortcuts_service.dart';
|
||||
import '../../mpv/player/player_android.dart';
|
||||
import '../../mpv/player/platform/player_android.dart';
|
||||
import '../../services/settings_service.dart' as settings;
|
||||
import '../../services/update_service.dart';
|
||||
import '../../utils/snackbar_helper.dart';
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
import '../mpv/mpv.dart';
|
||||
import '../mpv/player/player_android.dart';
|
||||
import '../mpv/player/platform/player_android.dart';
|
||||
|
||||
import '../../services/plex_client.dart';
|
||||
import '../models/livetv_channel.dart';
|
||||
@@ -24,7 +24,7 @@ import '../models/plex_media_info.dart';
|
||||
import '../providers/download_provider.dart';
|
||||
import '../providers/multi_server_provider.dart';
|
||||
import '../providers/playback_state_provider.dart';
|
||||
import '../models/companion_remote/remote_command_type.dart';
|
||||
import '../models/companion_remote/remote_command.dart';
|
||||
import '../providers/companion_remote_provider.dart';
|
||||
import '../services/companion_remote/companion_remote_receiver.dart';
|
||||
import '../services/fullscreen_state_manager.dart';
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'dart:math';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
|
||||
import '../../models/companion_remote/remote_command.dart';
|
||||
import '../../models/companion_remote/remote_command_type.dart';
|
||||
import '../../models/companion_remote/remote_session.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../models/companion_remote/remote_command.dart';
|
||||
import '../../models/companion_remote/remote_command_type.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
import '../../utils/key_event_simulator.dart';
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'fullscreen_state_manager.dart';
|
||||
import 'macos_window_delegate.dart';
|
||||
|
||||
/// Custom window delegate that manages fullscreen state
|
||||
/// Note: Window manipulation (toolbar, titlebar, traffic lights) is now handled
|
||||
/// directly in Swift's WindowDelegate. This class only updates Dart-side state.
|
||||
class FullscreenWindowDelegate extends MacOSWindowDelegate {
|
||||
@override
|
||||
void windowWillEnterFullScreen() {
|
||||
FullscreenStateManager().setFullscreen(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void windowDidExitFullScreen() {
|
||||
FullscreenStateManager().setFullscreen(false);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'dart:io' show Platform;
|
||||
import 'fullscreen_window_delegate.dart';
|
||||
import 'macos_window_service.dart';
|
||||
|
||||
/// Service to manage macOS titlebar configuration
|
||||
class MacOSTitlebarService {
|
||||
static bool _initialized = false;
|
||||
|
||||
/// Initialize the custom titlebar setup.
|
||||
///
|
||||
/// Note: The initial window configuration (transparent titlebar, toolbar,
|
||||
/// button positions, fullscreen presentation options) is now applied in
|
||||
/// MainFlutterWindow.swift / WindowDelegate.swift BEFORE frame restoration
|
||||
/// to prevent the window from shrinking on launch.
|
||||
///
|
||||
/// This method only sets up the Dart-side callbacks.
|
||||
static Future<void> setupCustomTitlebar() async {
|
||||
if (!Platform.isMacOS || _initialized) return;
|
||||
_initialized = true;
|
||||
|
||||
await MacOSWindowService.initialize(enableWindowDelegate: true);
|
||||
final delegate = FullscreenWindowDelegate();
|
||||
MacOSWindowService.addWindowDelegate(delegate);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/// Abstract class for receiving macOS window delegate callbacks.
|
||||
/// Extend this class and register with MacOSWindowService to receive
|
||||
/// fullscreen transition events.
|
||||
abstract class MacOSWindowDelegate {
|
||||
/// Called when the window is about to enter fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowWillEnterFullScreen() {}
|
||||
|
||||
/// Called when the window has entered fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowDidEnterFullScreen() {}
|
||||
|
||||
/// Called when the window is about to exit fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowWillExitFullScreen() {}
|
||||
|
||||
/// Called when the window has exited fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowDidExitFullScreen() {}
|
||||
}
|
||||
@@ -1,6 +1,27 @@
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'macos_window_delegate.dart';
|
||||
import 'fullscreen_state_manager.dart';
|
||||
|
||||
/// Abstract class for receiving macOS window delegate callbacks.
|
||||
/// Extend this class and register with [MacOSWindowService] to receive
|
||||
/// fullscreen transition events.
|
||||
abstract class MacOSWindowDelegate {
|
||||
/// Called when the window is about to enter fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowWillEnterFullScreen() {}
|
||||
|
||||
/// Called when the window has entered fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowDidEnterFullScreen() {}
|
||||
|
||||
/// Called when the window is about to exit fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowWillExitFullScreen() {}
|
||||
|
||||
/// Called when the window has exited fullscreen mode.
|
||||
// ignore: no-empty-block - default no-op, subclasses override as needed
|
||||
void windowDidExitFullScreen() {}
|
||||
}
|
||||
|
||||
/// Service for manipulating macOS window properties.
|
||||
/// This is a native implementation replacing the macos_window_utils package.
|
||||
@@ -45,6 +66,21 @@ class MacOSWindowService {
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
/// Initialize the window service and set up the titlebar.
|
||||
///
|
||||
/// Note: The initial window configuration (transparent titlebar, toolbar,
|
||||
/// button positions, fullscreen presentation options) is now applied in
|
||||
/// MainFlutterWindow.swift / WindowDelegate.swift BEFORE frame restoration
|
||||
/// to prevent the window from shrinking on launch.
|
||||
///
|
||||
/// This method sets up the Dart-side callbacks for fullscreen state tracking.
|
||||
static Future<void> setupCustomTitlebar() async {
|
||||
if (!Platform.isMacOS || _initialized) return;
|
||||
|
||||
await initialize(enableWindowDelegate: true);
|
||||
addWindowDelegate(_FullscreenWindowDelegate());
|
||||
}
|
||||
|
||||
/// Initialize the window service.
|
||||
/// Must be called before using other methods.
|
||||
/// Set [enableWindowDelegate] to true to receive fullscreen callbacks.
|
||||
@@ -94,3 +130,18 @@ class MacOSWindowService {
|
||||
return await _channel.invokeMethod<bool>('isFullscreen') ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal window delegate that manages fullscreen state.
|
||||
/// Note: Window manipulation (toolbar, titlebar, traffic lights) is now handled
|
||||
/// directly in Swift's WindowDelegate. This class only updates Dart-side state.
|
||||
class _FullscreenWindowDelegate extends MacOSWindowDelegate {
|
||||
@override
|
||||
void windowWillEnterFullScreen() {
|
||||
FullscreenStateManager().setFullscreen(true);
|
||||
}
|
||||
|
||||
@override
|
||||
void windowDidExitFullScreen() {
|
||||
FullscreenStateManager().setFullscreen(false);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import 'dart:io' show ProcessInfo;
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import '../../../../mpv/mpv.dart';
|
||||
import '../../../../mpv/player/player_android.dart';
|
||||
import '../../../../mpv/player/platform/player_android.dart';
|
||||
import '../../../../utils/app_logger.dart';
|
||||
import 'performance_stats.dart';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user