refactor: simplify macos titlebar logic
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import '../widgets/desktop_app_bar.dart';
|
import '../widgets/desktop_app_bar.dart';
|
||||||
|
import '../widgets/app_bar_back_button.dart';
|
||||||
|
|
||||||
class AboutScreen extends StatefulWidget {
|
class AboutScreen extends StatefulWidget {
|
||||||
const AboutScreen({super.key});
|
const AboutScreen({super.key});
|
||||||
@@ -38,17 +39,8 @@ class _AboutScreenState extends State<AboutScreen> {
|
|||||||
DesktopSliverAppBar(
|
DesktopSliverAppBar(
|
||||||
title: const Text('About'),
|
title: const Text('About'),
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: Container(
|
leading: const AppBarBackButton(
|
||||||
margin: const EdgeInsets.all(8),
|
style: BackButtonStyle.circular,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.5),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import '../models/plex_filter.dart';
|
|||||||
import '../models/plex_user_profile.dart';
|
import '../models/plex_user_profile.dart';
|
||||||
import '../widgets/media_card.dart';
|
import '../widgets/media_card.dart';
|
||||||
import '../widgets/desktop_app_bar.dart';
|
import '../widgets/desktop_app_bar.dart';
|
||||||
|
import '../widgets/app_bar_back_button.dart';
|
||||||
import '../services/storage_service.dart';
|
import '../services/storage_service.dart';
|
||||||
import '../mixins/refreshable.dart';
|
import '../mixins/refreshable.dart';
|
||||||
|
|
||||||
@@ -518,8 +519,8 @@ class _FiltersBottomSheetState extends State<_FiltersBottomSheet> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
AppBarBackButton(
|
||||||
icon: const Icon(Icons.arrow_back),
|
style: BackButtonStyle.plain,
|
||||||
onPressed: _goBack,
|
onPressed: _goBack,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import '../client/plex_client.dart';
|
|||||||
import '../models/plex_metadata.dart';
|
import '../models/plex_metadata.dart';
|
||||||
import '../models/plex_user_profile.dart';
|
import '../models/plex_user_profile.dart';
|
||||||
import '../widgets/desktop_app_bar.dart';
|
import '../widgets/desktop_app_bar.dart';
|
||||||
|
import '../widgets/app_bar_back_button.dart';
|
||||||
import '../widgets/media_context_menu.dart';
|
import '../widgets/media_context_menu.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
import 'season_detail_screen.dart';
|
import 'season_detail_screen.dart';
|
||||||
@@ -196,19 +197,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> {
|
|||||||
DesktopSliverAppBar(
|
DesktopSliverAppBar(
|
||||||
expandedHeight: headerHeight,
|
expandedHeight: headerHeight,
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: SafeArea(
|
leading: const AppBarBackButton(
|
||||||
child: Container(
|
style: BackButtonStyle.circular,
|
||||||
margin: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.5),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Stack(
|
background: Stack(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import '../client/plex_client.dart';
|
|||||||
import '../models/plex_metadata.dart';
|
import '../models/plex_metadata.dart';
|
||||||
import '../models/plex_user_profile.dart';
|
import '../models/plex_user_profile.dart';
|
||||||
import '../widgets/desktop_app_bar.dart';
|
import '../widgets/desktop_app_bar.dart';
|
||||||
|
import '../widgets/app_bar_back_button.dart';
|
||||||
import 'video_player_screen.dart';
|
import 'video_player_screen.dart';
|
||||||
|
|
||||||
class SeasonDetailScreen extends StatefulWidget {
|
class SeasonDetailScreen extends StatefulWidget {
|
||||||
@@ -58,17 +59,8 @@ class _SeasonDetailScreenState extends State<SeasonDetailScreen> {
|
|||||||
DesktopSliverAppBar(
|
DesktopSliverAppBar(
|
||||||
title: Text(widget.season.title),
|
title: Text(widget.season.title),
|
||||||
pinned: true,
|
pinned: true,
|
||||||
leading: Container(
|
leading: const AppBarBackButton(
|
||||||
margin: const EdgeInsets.all(8),
|
style: BackButtonStyle.circular,
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black.withValues(alpha: 0.5),
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
),
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_isLoadingEpisodes)
|
if (_isLoadingEpisodes)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import '../services/storage_service.dart';
|
|||||||
import '../client/plex_client.dart';
|
import '../client/plex_client.dart';
|
||||||
import '../config/plex_config.dart';
|
import '../config/plex_config.dart';
|
||||||
import '../widgets/server_list_tile.dart';
|
import '../widgets/server_list_tile.dart';
|
||||||
|
import '../widgets/desktop_app_bar.dart';
|
||||||
import 'main_screen.dart';
|
import 'main_screen.dart';
|
||||||
|
|
||||||
class ServerSelectionScreen extends StatefulWidget {
|
class ServerSelectionScreen extends StatefulWidget {
|
||||||
@@ -114,7 +115,7 @@ class _ServerSelectionScreenState extends State<ServerSelectionScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('Select Server')),
|
appBar: const DesktopAppBar(title: Text('Select Server')),
|
||||||
body: _isLoading
|
body: _isLoading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: _errorMessage != null
|
: _errorMessage != null
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../services/fullscreen_state_manager.dart';
|
||||||
|
|
||||||
|
/// Padding values for desktop window controls
|
||||||
|
class DesktopWindowPadding {
|
||||||
|
/// Left padding for macOS traffic lights (normal window mode)
|
||||||
|
static const double macOSLeft = 80.0;
|
||||||
|
|
||||||
|
/// Left padding for macOS in fullscreen (reduced since traffic lights auto-hide)
|
||||||
|
static const double macOSLeftFullscreen = 0.0;
|
||||||
|
|
||||||
|
/// Right padding for macOS to prevent actions from being too close to edge
|
||||||
|
static const double macOSRight = 16.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper class for adjusting app bar widgets to account for desktop window controls
|
||||||
|
class DesktopAppBarHelper {
|
||||||
|
/// Builds actions list with appropriate right padding for macOS
|
||||||
|
static List<Widget>? buildAdjustedActions(List<Widget>? actions) {
|
||||||
|
if (!Platform.isMacOS) {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// macOS: Add padding to keep actions away from edge
|
||||||
|
if (actions != null) {
|
||||||
|
return [
|
||||||
|
...actions,
|
||||||
|
SizedBox(width: DesktopWindowPadding.macOSRight),
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [SizedBox(width: DesktopWindowPadding.macOSRight)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds leading widget with appropriate left padding for macOS traffic lights
|
||||||
|
///
|
||||||
|
/// [includeGestureDetector] - If true, wraps in GestureDetector to prevent window dragging
|
||||||
|
static Widget? buildAdjustedLeading(
|
||||||
|
Widget? leading, {
|
||||||
|
bool includeGestureDetector = false,
|
||||||
|
}) {
|
||||||
|
if (!Platform.isMacOS || leading == null) {
|
||||||
|
return leading;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: FullscreenStateManager(),
|
||||||
|
builder: (context, _) {
|
||||||
|
final isFullscreen = FullscreenStateManager().isFullscreen;
|
||||||
|
final leftPadding = isFullscreen
|
||||||
|
? DesktopWindowPadding.macOSLeftFullscreen
|
||||||
|
: DesktopWindowPadding.macOSLeft;
|
||||||
|
|
||||||
|
final paddedWidget = Padding(
|
||||||
|
padding: EdgeInsets.only(left: leftPadding),
|
||||||
|
child: leading,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (includeGestureDetector) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onPanDown: (_) {}, // Consume pan gestures to prevent window dragging
|
||||||
|
child: paddedWidget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return paddedWidget;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds flexible space with gesture detector on macOS to prevent window dragging
|
||||||
|
static Widget? buildAdjustedFlexibleSpace(Widget? flexibleSpace) {
|
||||||
|
if (!Platform.isMacOS || flexibleSpace == null) {
|
||||||
|
return flexibleSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onPanDown: (_) {}, // Consume pan gestures to prevent window dragging
|
||||||
|
child: flexibleSpace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the leading width for SliverAppBar to account for macOS traffic lights
|
||||||
|
static double? calculateLeadingWidth(Widget? leading) {
|
||||||
|
if (!Platform.isMacOS || leading == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isFullscreen = FullscreenStateManager().isFullscreen;
|
||||||
|
final leftPadding = isFullscreen
|
||||||
|
? DesktopWindowPadding.macOSLeftFullscreen
|
||||||
|
: DesktopWindowPadding.macOSLeft;
|
||||||
|
return leftPadding + kToolbarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps a widget with GestureDetector on macOS to prevent window dragging
|
||||||
|
static Widget wrapWithGestureDetector(Widget child) {
|
||||||
|
if (!Platform.isMacOS) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onPanDown: (_) {}, // Consume pan gestures to prevent window dragging
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A widget that adds padding to account for desktop window controls.
|
||||||
|
/// On macOS, adds left padding for traffic lights (reduced in fullscreen).
|
||||||
|
class DesktopTitleBarPadding extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final double? leftPadding;
|
||||||
|
final double? rightPadding;
|
||||||
|
|
||||||
|
const DesktopTitleBarPadding({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.leftPadding,
|
||||||
|
this.rightPadding,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!Platform.isMacOS) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: FullscreenStateManager(),
|
||||||
|
builder: (context, _) {
|
||||||
|
final isFullscreen = FullscreenStateManager().isFullscreen;
|
||||||
|
// In fullscreen, use minimal padding since traffic lights auto-hide
|
||||||
|
final left = leftPadding ??
|
||||||
|
(isFullscreen
|
||||||
|
? DesktopWindowPadding.macOSLeftFullscreen
|
||||||
|
: DesktopWindowPadding.macOSLeft);
|
||||||
|
final right = rightPadding ?? 0.0;
|
||||||
|
|
||||||
|
if (left == 0.0 && right == 0.0) {
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(left: left, right: right),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Defines the visual style of the back button
|
||||||
|
enum BackButtonStyle {
|
||||||
|
/// Back button with circular semi-transparent background (used in detail screens)
|
||||||
|
circular,
|
||||||
|
|
||||||
|
/// Plain back button without background (used in sheets and simple contexts)
|
||||||
|
plain,
|
||||||
|
|
||||||
|
/// Back button styled for video player overlay
|
||||||
|
video,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A reusable back button widget that provides consistent styling across the app.
|
||||||
|
///
|
||||||
|
/// This widget supports different visual styles through [BackButtonStyle] enum:
|
||||||
|
/// - [BackButtonStyle.circular]: Semi-transparent circular background for detail screens
|
||||||
|
/// - [BackButtonStyle.plain]: Simple IconButton for sheets and simple contexts
|
||||||
|
/// - [BackButtonStyle.video]: Styled for video player overlay
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```dart
|
||||||
|
/// AppBarBackButton(style: BackButtonStyle.circular)
|
||||||
|
/// ```
|
||||||
|
class AppBarBackButton extends StatelessWidget {
|
||||||
|
/// Creates a back button with the specified style.
|
||||||
|
///
|
||||||
|
/// [style] determines the visual appearance of the back button.
|
||||||
|
/// [onPressed] is called when the button is tapped. If null, defaults to Navigator.pop.
|
||||||
|
/// [color] overrides the default icon color. If null, uses white for circular/video, theme default for plain.
|
||||||
|
const AppBarBackButton({
|
||||||
|
super.key,
|
||||||
|
this.style = BackButtonStyle.circular,
|
||||||
|
this.onPressed,
|
||||||
|
this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The visual style of the back button
|
||||||
|
final BackButtonStyle style;
|
||||||
|
|
||||||
|
/// Callback when the button is pressed. Defaults to Navigator.of(context).pop()
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
/// The color of the back arrow icon. If null, uses style-appropriate default.
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
void _handlePressed(BuildContext context) {
|
||||||
|
if (onPressed != null) {
|
||||||
|
onPressed!();
|
||||||
|
} else {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
switch (style) {
|
||||||
|
case BackButtonStyle.circular:
|
||||||
|
return _buildCircularBackButton(context);
|
||||||
|
case BackButtonStyle.plain:
|
||||||
|
return _buildPlainBackButton(context);
|
||||||
|
case BackButtonStyle.video:
|
||||||
|
return _buildVideoBackButton(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a back button with circular semi-transparent background
|
||||||
|
Widget _buildCircularBackButton(BuildContext context) {
|
||||||
|
return SafeArea(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.5),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: color ?? Colors.white),
|
||||||
|
onPressed: () => _handlePressed(context),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: 'Back',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a plain back button without background
|
||||||
|
Widget _buildPlainBackButton(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: color),
|
||||||
|
onPressed: () => _handlePressed(context),
|
||||||
|
tooltip: 'Back',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a back button styled for video player overlay
|
||||||
|
Widget _buildVideoBackButton(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(left: 8),
|
||||||
|
child: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: color ?? Colors.white),
|
||||||
|
onPressed: () => _handlePressed(context),
|
||||||
|
tooltip: 'Back',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,62 +1,5 @@
|
|||||||
import 'dart:io' show Platform;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../services/fullscreen_state_manager.dart';
|
import '../utils/desktop_window_padding.dart';
|
||||||
|
|
||||||
class DesktopWindowPadding {
|
|
||||||
/// Left padding for macOS traffic lights (normal window mode)
|
|
||||||
static const double macOSLeft = 80.0;
|
|
||||||
|
|
||||||
/// Left padding for macOS in fullscreen (reduced since traffic lights auto-hide)
|
|
||||||
static const double macOSLeftFullscreen = 0.0;
|
|
||||||
|
|
||||||
/// Right padding for macOS to prevent actions from being too close to edge
|
|
||||||
static const double macOSRight = 16.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A widget that adds padding to account for desktop window controls.
|
|
||||||
/// On macOS, adds left padding for traffic lights (reduced in fullscreen).
|
|
||||||
class DesktopTitleBarPadding extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
final double? leftPadding;
|
|
||||||
final double? rightPadding;
|
|
||||||
|
|
||||||
const DesktopTitleBarPadding({
|
|
||||||
super.key,
|
|
||||||
required this.child,
|
|
||||||
this.leftPadding,
|
|
||||||
this.rightPadding,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListenableBuilder(
|
|
||||||
listenable: FullscreenStateManager(),
|
|
||||||
builder: (context, _) {
|
|
||||||
double left = 0.0;
|
|
||||||
double right = 0.0;
|
|
||||||
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
final isFullscreen = FullscreenStateManager().isFullscreen;
|
|
||||||
// In fullscreen, use minimal padding since traffic lights auto-hide
|
|
||||||
left =
|
|
||||||
leftPadding ??
|
|
||||||
(isFullscreen
|
|
||||||
? DesktopWindowPadding.macOSLeftFullscreen
|
|
||||||
: DesktopWindowPadding.macOSLeft);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left == 0.0 && right == 0.0) {
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(left: left, right: right),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A custom app bar that automatically handles desktop window controls spacing.
|
/// A custom app bar that automatically handles desktop window controls spacing.
|
||||||
/// Use this instead of AppBar for consistent desktop platform behavior.
|
/// Use this instead of AppBar for consistent desktop platform behavior.
|
||||||
@@ -86,43 +29,10 @@ class DesktopAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Add right padding for desktop platforms
|
|
||||||
List<Widget>? adjustedActions = actions;
|
|
||||||
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
// macOS: Add padding to keep actions away from edge
|
|
||||||
if (actions != null) {
|
|
||||||
adjustedActions = [
|
|
||||||
...actions!,
|
|
||||||
SizedBox(width: DesktopWindowPadding.macOSRight),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
adjustedActions = [SizedBox(width: DesktopWindowPadding.macOSRight)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap leading widget with padding on macOS to avoid traffic lights
|
|
||||||
Widget? adjustedLeading = leading;
|
|
||||||
if (Platform.isMacOS && leading != null) {
|
|
||||||
adjustedLeading = ListenableBuilder(
|
|
||||||
listenable: FullscreenStateManager(),
|
|
||||||
builder: (context, _) {
|
|
||||||
final isFullscreen = FullscreenStateManager().isFullscreen;
|
|
||||||
final leftPadding = isFullscreen
|
|
||||||
? DesktopWindowPadding.macOSLeftFullscreen
|
|
||||||
: DesktopWindowPadding.macOSLeft;
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(left: leftPadding),
|
|
||||||
child: leading,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final appBar = AppBar(
|
final appBar = AppBar(
|
||||||
title: title != null ? DesktopTitleBarPadding(child: title!) : null,
|
title: title != null ? DesktopTitleBarPadding(child: title!) : null,
|
||||||
actions: adjustedActions,
|
actions: DesktopAppBarHelper.buildAdjustedActions(actions),
|
||||||
leading: adjustedLeading,
|
leading: DesktopAppBarHelper.buildAdjustedLeading(leading),
|
||||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
@@ -131,17 +41,7 @@ class DesktopAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
scrolledUnderElevation: scrolledUnderElevation,
|
scrolledUnderElevation: scrolledUnderElevation,
|
||||||
);
|
);
|
||||||
|
|
||||||
// On macOS with transparent titlebar, wrap in GestureDetector to prevent
|
return DesktopAppBarHelper.wrapWithGestureDetector(appBar);
|
||||||
// window dragging and allow buttons to be clickable
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onPanDown: (_) {}, // Consume pan gestures to prevent window dragging
|
|
||||||
child: appBar,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return appBar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -186,69 +86,14 @@ class DesktopSliverAppBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Add right padding for desktop platforms
|
|
||||||
List<Widget>? adjustedActions = actions;
|
|
||||||
|
|
||||||
if (Platform.isMacOS) {
|
|
||||||
// macOS: Add padding to keep actions away from edge
|
|
||||||
if (actions != null) {
|
|
||||||
adjustedActions = [
|
|
||||||
...actions!,
|
|
||||||
SizedBox(width: DesktopWindowPadding.macOSRight),
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
adjustedActions = [SizedBox(width: DesktopWindowPadding.macOSRight)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap leading widget with gesture detector and padding on macOS
|
|
||||||
Widget? adjustedLeading = leading;
|
|
||||||
if (Platform.isMacOS && leading != null) {
|
|
||||||
adjustedLeading = ListenableBuilder(
|
|
||||||
listenable: FullscreenStateManager(),
|
|
||||||
builder: (context, _) {
|
|
||||||
final isFullscreen = FullscreenStateManager().isFullscreen;
|
|
||||||
final leftPadding = isFullscreen
|
|
||||||
? DesktopWindowPadding.macOSLeftFullscreen
|
|
||||||
: DesktopWindowPadding.macOSLeft;
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onPanDown:
|
|
||||||
(_) {}, // Consume pan gestures to prevent window dragging
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(left: leftPadding),
|
|
||||||
child: leading,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap flexible space with gesture detector on macOS to prevent window dragging
|
|
||||||
Widget? adjustedFlexibleSpace = flexibleSpace;
|
|
||||||
if (Platform.isMacOS && flexibleSpace != null) {
|
|
||||||
adjustedFlexibleSpace = GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onPanDown: (_) {}, // Consume pan gestures to prevent window dragging
|
|
||||||
child: flexibleSpace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// On macOS, increase leading width to account for traffic light spacing
|
|
||||||
double? leadingWidth;
|
|
||||||
if (Platform.isMacOS && leading != null) {
|
|
||||||
final isFullscreen = FullscreenStateManager().isFullscreen;
|
|
||||||
final leftPadding = isFullscreen
|
|
||||||
? DesktopWindowPadding.macOSLeftFullscreen
|
|
||||||
: DesktopWindowPadding.macOSLeft;
|
|
||||||
leadingWidth = leftPadding + kToolbarHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
title: title != null ? DesktopTitleBarPadding(child: title!) : null,
|
title: title != null ? DesktopTitleBarPadding(child: title!) : null,
|
||||||
actions: adjustedActions,
|
actions: DesktopAppBarHelper.buildAdjustedActions(actions),
|
||||||
leading: adjustedLeading,
|
leading: DesktopAppBarHelper.buildAdjustedLeading(
|
||||||
leadingWidth: leadingWidth,
|
leading,
|
||||||
|
includeGestureDetector: true,
|
||||||
|
),
|
||||||
|
leadingWidth: DesktopAppBarHelper.calculateLeadingWidth(leading),
|
||||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||||
elevation: elevation,
|
elevation: elevation,
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
@@ -258,7 +103,7 @@ class DesktopSliverAppBar extends StatelessWidget {
|
|||||||
floating: floating,
|
floating: floating,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
expandedHeight: expandedHeight,
|
expandedHeight: expandedHeight,
|
||||||
flexibleSpace: adjustedFlexibleSpace,
|
flexibleSpace: DesktopAppBarHelper.buildAdjustedFlexibleSpace(flexibleSpace),
|
||||||
bottom: bottom,
|
bottom: bottom,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,8 +326,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
|||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
AppBarBackButton(
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
style: BackButtonStyle.video,
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
@@ -547,8 +547,8 @@ class _PlexVideoControlsState extends State<PlexVideoControls>
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
AppBarBackButton(
|
||||||
icon: const Icon(Icons.arrow_back, color: Colors.white),
|
style: BackButtonStyle.video,
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|||||||
Reference in New Issue
Block a user