From 5930c342b5be95c58b2d3884f738b7990abcb46d Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 12 Jan 2025 21:32:33 +0600 Subject: [PATCH] chore: fix window resizing --- lib/collections/assets.gen.dart | 13 -- lib/components/titlebar/mouse_state.dart | 73 --------- lib/components/titlebar/titlebar.dart | 17 ++- lib/components/titlebar/titlebar_buttons.dart | 144 +++++++++--------- .../titlebar/titlebar_icon_buttons.dart | 95 ++++++------ lib/components/titlebar/window_button.dart | 125 --------------- lib/main.dart | 4 +- lib/pages/home/home.dart | 2 +- lib/pages/library/library.dart | 8 - lib/pages/lyrics/lyrics.dart | 2 + lib/pages/search/search.dart | 3 +- 11 files changed, 132 insertions(+), 354 deletions(-) delete mode 100644 lib/components/titlebar/mouse_state.dart delete mode 100644 lib/components/titlebar/window_button.dart diff --git a/lib/collections/assets.gen.dart b/lib/collections/assets.gen.dart index 98b67c80..004001f2 100644 --- a/lib/collections/assets.gen.dart +++ b/lib/collections/assets.gen.dart @@ -20,17 +20,6 @@ class $AssetsBackgroundsGen { List get values => [xmasEffect]; } -class $AssetsIllustrationsGen { - const $AssetsIllustrationsGen(); - - /// File path: assets/illustrations/fixing_bugs.png - AssetGenImage get fixingBugs => - const AssetGenImage('assets/illustrations/fixing_bugs.png'); - - /// List of all assets - List get values => [fixingBugs]; -} - class $AssetsLogosGen { const $AssetsLogosGen(); @@ -151,8 +140,6 @@ class Assets { AssetGenImage('assets/bengali-patterns-bg.jpg'); static const AssetGenImage branding = AssetGenImage('assets/branding.png'); static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png'); - static const $AssetsIllustrationsGen illustrations = - $AssetsIllustrationsGen(); static const AssetGenImage invidious = AssetGenImage('assets/invidious.jpg'); static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png'); static const AssetGenImage likedTracks = diff --git a/lib/components/titlebar/mouse_state.dart b/lib/components/titlebar/mouse_state.dart deleted file mode 100644 index 9af2a8b0..00000000 --- a/lib/components/titlebar/mouse_state.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter/material.dart'; - -typedef MouseStateBuilderCB = Widget Function( - BuildContext context, MouseState mouseState); - -class MouseState { - bool isMouseOver = false; - bool isMouseDown = false; - MouseState(); - @override - String toString() { - return "isMouseDown: $isMouseDown - isMouseOver: $isMouseOver"; - } -} - -T? _ambiguate(T? value) => value; - -class MouseStateBuilder extends StatefulWidget { - final MouseStateBuilderCB builder; - final VoidCallback? onPressed; - const MouseStateBuilder({super.key, required this.builder, this.onPressed}); - @override - // ignore: library_private_types_in_public_api - _MouseStateBuilderState createState() => _MouseStateBuilderState(); -} - -class _MouseStateBuilderState extends State { - late MouseState _mouseState; - _MouseStateBuilderState() { - _mouseState = MouseState(); - } - - @override - Widget build(BuildContext context) { - return MouseRegion( - onEnter: (event) { - setState(() { - _mouseState.isMouseOver = true; - }); - }, - onExit: (event) { - setState(() { - _mouseState.isMouseOver = false; - }); - }, - child: GestureDetector( - onTapDown: (_) { - setState(() { - _mouseState.isMouseDown = true; - }); - }, - onTapCancel: () { - setState(() { - _mouseState.isMouseDown = false; - }); - }, - onTap: () { - setState(() { - _mouseState.isMouseDown = false; - _mouseState.isMouseOver = false; - }); - _ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) { - if (widget.onPressed != null) { - widget.onPressed!(); - } - }); - }, - onTapUp: (_) {}, - child: widget.builder(context, _mouseState), - ), - ); - } -} diff --git a/lib/components/titlebar/titlebar.dart b/lib/components/titlebar/titlebar.dart index 215b63ed..5b86f6ad 100644 --- a/lib/components/titlebar/titlebar.dart +++ b/lib/components/titlebar/titlebar.dart @@ -1,6 +1,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/titlebar/titlebar_buttons.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -49,7 +50,7 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { this.height, this.surfaceBlur, this.surfaceOpacity, - this.useSafeArea = true, + this.useSafeArea = false, }); void onDrag(WidgetRef ref) { @@ -66,7 +67,7 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { final lastClicked = useRef(DateTime.now().millisecondsSinceEpoch); return SizedBox( - height: height ?? 56, + height: height ?? (48 * context.theme.scaling), child: LayoutBuilder( builder: (context, constraints) { final hasFullscreen = @@ -102,18 +103,22 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { : leading, trailing: [ ...trailing, - WindowTitleBarButtons(foregroundColor: foregroundColor), + Align( + alignment: Alignment.topRight, + child: + WindowTitleBarButtons(foregroundColor: foregroundColor), + ), ], title: title, header: header, subtitle: subtitle, trailingExpanded: trailingExpanded, alignment: alignment, - padding: padding, + padding: padding ?? EdgeInsets.zero, backgroundColor: backgroundColor, leadingGap: leadingGap, trailingGap: trailingGap, - height: height, + height: height ?? (48 * context.theme.scaling), surfaceBlur: surfaceBlur, surfaceOpacity: surfaceOpacity, useSafeArea: useSafeArea, @@ -127,5 +132,5 @@ class TitleBar extends HookConsumerWidget implements PreferredSizeWidget { } @override - Size get preferredSize => Size.fromHeight(height ?? 56.0); + Size get preferredSize => Size.fromHeight(height ?? 48); } diff --git a/lib/components/titlebar/titlebar_buttons.dart b/lib/components/titlebar/titlebar_buttons.dart index 42765d7b..92c2c93d 100644 --- a/lib/components/titlebar/titlebar_buttons.dart +++ b/lib/components/titlebar/titlebar_buttons.dart @@ -2,9 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/components/hover_builder.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/titlebar/titlebar_icon_buttons.dart'; -import 'package:spotube/components/titlebar/window_button.dart'; + +import 'package:spotube/hooks/configurators/use_window_listener.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/utils/platform.dart'; import 'package:titlebar_buttons/titlebar_buttons.dart'; @@ -22,12 +24,20 @@ class WindowTitleBarButtons extends HookConsumerWidget { final preferences = ref.watch(userPreferencesProvider); final isMaximized = useState(null); const type = ThemeType.auto; - final scale = context.theme.scaling; Future onClose() async { await windowManager.close(); } + useWindowListener( + onWindowMaximize: () { + isMaximized.value = true; + }, + onWindowUnmaximize: () { + isMaximized.value = false; + }, + ); + useEffect(() { if (kIsDesktop) { windowManager.isMaximized().then((value) { @@ -42,86 +52,68 @@ class WindowTitleBarButtons extends HookConsumerWidget { } if (kIsWindows) { - final theme = Theme.of(context); - final colors = WindowButtonColors( - normal: Colors.transparent, - iconNormal: foregroundColor ?? theme.colorScheme.onSurface, - mouseOver: theme.colorScheme.onSurface.withAlpha(25), - mouseDown: theme.colorScheme.onSurface.withAlpha(51), - iconMouseOver: theme.colorScheme.onSurface, - iconMouseDown: theme.colorScheme.onSurface, - ); - - final closeColors = WindowButtonColors( - normal: Colors.transparent, - iconNormal: foregroundColor ?? theme.colorScheme.onSurface, - mouseOver: Colors.red, - mouseDown: Colors.red[800]!, - iconMouseOver: Colors.white, - iconMouseDown: Colors.black, - ); - - return Transform( - transform: Matrix4.translationValues(18, -12, 0) * scale, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MinimizeWindowButton( - onPressed: windowManager.minimize, - colors: colors, + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShadcnWindowButton( + icon: MinimizeIcon(color: context.theme.colorScheme.foreground), + onPressed: windowManager.minimize, + ), + if (isMaximized.value != true) + ShadcnWindowButton( + icon: MaximizeIcon(color: context.theme.colorScheme.foreground), + onPressed: () { + windowManager.maximize(); + isMaximized.value = true; + }, + ) + else + ShadcnWindowButton( + icon: RestoreIcon(color: context.theme.colorScheme.foreground), + onPressed: () { + windowManager.unmaximize(); + isMaximized.value = false; + }, ), - if (isMaximized.value != true) - MaximizeWindowButton( - colors: colors, - onPressed: () { - windowManager.maximize(); - isMaximized.value = true; - }, - ) - else - RestoreWindowButton( - colors: colors, - onPressed: () { - windowManager.unmaximize(); - isMaximized.value = false; - }, + HoverBuilder(builder: (context, isHovered) { + return ShadcnWindowButton( + icon: CloseIcon( + color: isHovered + ? Colors.white + : context.theme.colorScheme.foreground, ), - CloseWindowButton( - colors: closeColors, onPressed: onClose, - ), - ], - ), + hoverBackgroundColor: const Color(0xFFD32F2F), + ); + }), + ], ); } - return Transform( - transform: Matrix4.translationValues(18, -12, 0) * scale, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DecoratedMinimizeButton( - type: type, - onPressed: windowManager.minimize, - ), - DecoratedMaximizeButton( - type: type, - onPressed: () async { - if (await windowManager.isMaximized()) { - await windowManager.unmaximize(); - isMaximized.value = false; - } else { - await windowManager.maximize(); - isMaximized.value = true; - } - }, - ), - DecoratedCloseButton( - type: type, - onPressed: onClose, - ), - ], - ), + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DecoratedMinimizeButton( + type: type, + onPressed: windowManager.minimize, + ), + DecoratedMaximizeButton( + type: type, + onPressed: () async { + if (await windowManager.isMaximized()) { + await windowManager.unmaximize(); + isMaximized.value = false; + } else { + await windowManager.maximize(); + isMaximized.value = true; + } + }, + ), + DecoratedCloseButton( + type: type, + onPressed: onClose, + ), + ], ); } } diff --git a/lib/components/titlebar/titlebar_icon_buttons.dart b/lib/components/titlebar/titlebar_icon_buttons.dart index 70170262..481a22ce 100644 --- a/lib/components/titlebar/titlebar_icon_buttons.dart +++ b/lib/components/titlebar/titlebar_icon_buttons.dart @@ -1,56 +1,50 @@ import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:spotube/components/titlebar/window_button.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/extensions/button_variance.dart'; -class MinimizeWindowButton extends WindowButton { - MinimizeWindowButton( - {super.key, super.colors, super.onPressed, bool? animate}) - : super( - animate: animate ?? false, - iconBuilder: (buttonContext) => - MinimizeIcon(color: buttonContext.iconColor), - ); +class ShadcnWindowButton extends StatelessWidget { + final Widget icon; + final VoidCallback onPressed; + final Color? hoverBackgroundColor; + + const ShadcnWindowButton({ + super.key, + required this.icon, + required this.onPressed, + this.hoverBackgroundColor, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 45, + height: 32, + child: IconButton( + variance: ButtonVariance.ghost.copyWith( + decoration: (context, states) { + final decoration = ButtonVariance.ghost.decoration(context, states) + as BoxDecoration; + if (hoverBackgroundColor != null && + states.contains(WidgetState.hovered)) { + return decoration.copyWith( + borderRadius: BorderRadius.zero, + color: hoverBackgroundColor, + ); + } + + return decoration.copyWith( + borderRadius: BorderRadius.zero, + ); + }, + ), + icon: icon, + onPressed: onPressed, + ), + ); + } } -class MaximizeWindowButton extends WindowButton { - MaximizeWindowButton( - {super.key, super.colors, super.onPressed, bool? animate}) - : super( - animate: animate ?? false, - iconBuilder: (buttonContext) => - MaximizeIcon(color: buttonContext.iconColor), - ); -} - -class RestoreWindowButton extends WindowButton { - RestoreWindowButton({super.key, super.colors, super.onPressed, bool? animate}) - : super( - animate: animate ?? false, - iconBuilder: (buttonContext) => - RestoreIcon(color: buttonContext.iconColor), - ); -} - -final _defaultCloseButtonColors = WindowButtonColors( - mouseOver: const Color(0xFFD32F2F), - mouseDown: const Color(0xFFB71C1C), - iconNormal: const Color(0xFF805306), - iconMouseOver: const Color(0xFFFFFFFF)); - -class CloseWindowButton extends WindowButton { - CloseWindowButton( - {super.key, WindowButtonColors? colors, super.onPressed, bool? animate}) - : super( - colors: colors ?? _defaultCloseButtonColors, - animate: animate ?? false, - iconBuilder: (buttonContext) => - CloseIcon(color: buttonContext.iconColor), - ); -} - -// Switched to CustomPaint icons by https://github.com/esDotDev - /// Close class CloseIcon extends StatelessWidget { final Color color; @@ -149,8 +143,9 @@ class _AlignedPaint extends StatelessWidget { @override Widget build(BuildContext context) { return Align( - alignment: Alignment.center, - child: CustomPaint(size: const Size(10, 10), painter: painter)); + alignment: Alignment.center, + child: CustomPaint(size: const Size(10, 10), painter: painter), + ); } } diff --git a/lib/components/titlebar/window_button.dart b/lib/components/titlebar/window_button.dart deleted file mode 100644 index fef9d754..00000000 --- a/lib/components/titlebar/window_button.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/components/titlebar/mouse_state.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; - -typedef WindowButtonIconBuilder = Widget Function( - WindowButtonContext buttonContext); -typedef WindowButtonBuilder = Widget Function( - WindowButtonContext buttonContext, Widget icon); - -class WindowButtonContext { - BuildContext context; - MouseState mouseState; - Color? backgroundColor; - Color iconColor; - WindowButtonContext( - {required this.context, - required this.mouseState, - this.backgroundColor, - required this.iconColor}); -} - -class WindowButtonColors { - late Color normal; - late Color mouseOver; - late Color mouseDown; - late Color iconNormal; - late Color iconMouseOver; - late Color iconMouseDown; - WindowButtonColors( - {Color? normal, - Color? mouseOver, - Color? mouseDown, - Color? iconNormal, - Color? iconMouseOver, - Color? iconMouseDown}) { - this.normal = normal ?? _defaultButtonColors.normal; - this.mouseOver = mouseOver ?? _defaultButtonColors.mouseOver; - this.mouseDown = mouseDown ?? _defaultButtonColors.mouseDown; - this.iconNormal = iconNormal ?? _defaultButtonColors.iconNormal; - this.iconMouseOver = iconMouseOver ?? _defaultButtonColors.iconMouseOver; - this.iconMouseDown = iconMouseDown ?? _defaultButtonColors.iconMouseDown; - } -} - -final _defaultButtonColors = WindowButtonColors( - normal: Colors.transparent, - iconNormal: const Color(0xFF805306), - mouseOver: const Color(0xFF404040), - mouseDown: const Color(0xFF202020), - iconMouseOver: const Color(0xFFFFFFFF), - iconMouseDown: const Color(0xFFF0F0F0), -); - -class WindowButton extends StatelessWidget { - final WindowButtonBuilder? builder; - final WindowButtonIconBuilder? iconBuilder; - late final WindowButtonColors colors; - final bool animate; - final EdgeInsets? padding; - final VoidCallback? onPressed; - - WindowButton( - {super.key, - WindowButtonColors? colors, - this.builder, - @required this.iconBuilder, - this.padding, - this.onPressed, - this.animate = false}) { - this.colors = colors ?? _defaultButtonColors; - } - - Color getBackgroundColor(MouseState mouseState) { - if (mouseState.isMouseDown) return colors.mouseDown; - if (mouseState.isMouseOver) return colors.mouseOver; - return colors.normal; - } - - Color getIconColor(MouseState mouseState) { - if (mouseState.isMouseDown) return colors.iconMouseDown; - if (mouseState.isMouseOver) return colors.iconMouseOver; - return colors.iconNormal; - } - - @override - Widget build(BuildContext context) { - if (!kTitlebarVisible) return const SizedBox.shrink(); - - return MouseStateBuilder( - builder: (context, mouseState) { - WindowButtonContext buttonContext = WindowButtonContext( - mouseState: mouseState, - context: context, - backgroundColor: getBackgroundColor(mouseState), - iconColor: getIconColor(mouseState)); - - var icon = (iconBuilder != null) - ? iconBuilder!(buttonContext) - : const SizedBox(); - - var fadeOutColor = - getBackgroundColor(MouseState()..isMouseOver = true).withAlpha(0); - var padding = this.padding ?? const EdgeInsets.all(10); - var animationMs = - mouseState.isMouseOver ? (animate ? 100 : 0) : (animate ? 200 : 0); - Widget iconWithPadding = Padding(padding: padding, child: icon); - iconWithPadding = AnimatedContainer( - curve: Curves.easeOut, - duration: Duration(milliseconds: animationMs), - color: buttonContext.backgroundColor ?? fadeOutColor, - child: iconWithPadding); - var button = - (builder != null) ? builder!(buttonContext, icon) : iconWithPadding; - return SizedBox( - width: 45, - height: 32, - child: button, - ); - }, - onPressed: () { - if (onPressed != null) onPressed!(); - }, - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index 5b20acd8..8025f7cc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -207,7 +207,9 @@ class Spotube extends HookConsumerWidget { child: child!, ); - if (kIsDesktop && !kIsMacOS) child = DragToResizeArea(child: child); + if (kIsLinux) { + child = DragToResizeArea(child: child); + } return child; }, diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 78371c4d..1638393b 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -36,7 +36,7 @@ class HomePage extends HookConsumerWidget { bottom: false, child: Scaffold( headers: [ - if (kTitlebarVisible) const TitleBar(), + if (kTitlebarVisible) const TitleBar(height: 30), ], child: CustomScrollView( controller: controller, diff --git a/lib/pages/library/library.dart b/lib/pages/library/library.dart index 87b6a6cb..ec896228 100644 --- a/lib/pages/library/library.dart +++ b/lib/pages/library/library.dart @@ -2,8 +2,6 @@ import 'package:flutter/material.dart' show Badge; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; - import 'package:spotube/modules/library/user_local_tracks.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/library/user_albums.dart'; @@ -19,7 +17,6 @@ class LibraryPage extends HookConsumerWidget { const LibraryPage({super.key}); @override Widget build(BuildContext context, ref) { - final scale = context.theme.scaling; final downloadingCount = ref.watch(downloadManagerProvider).$downloadCount; final index = useState(0); @@ -40,11 +37,6 @@ class LibraryPage extends HookConsumerWidget { child: Scaffold( headers: [ TitleBar( - padding: const EdgeInsets.symmetric( - horizontal: 18, - vertical: 12, - ).copyWith(left: 0) * - scale, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: TabList( diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index 679ef78e..f61dbbe3 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -151,6 +151,8 @@ class LyricsPage extends HookConsumerWidget { ? TitleBar( backgroundColor: Colors.transparent, title: tabbar, + height: 58 * context.theme.scaling, + surfaceBlur: 0, ) : tabbar ], diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index efd46ebb..701c3c5c 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -70,7 +70,8 @@ class SearchPage extends HookConsumerWidget { bottom: false, child: Scaffold( headers: [ - if (kTitlebarVisible) const TitleBar(automaticallyImplyLeading: true) + if (kTitlebarVisible) + const TitleBar(automaticallyImplyLeading: true, height: 30) ], child: auth.asData?.value == null ? const AnonymousFallback()