chore: fix window resizing

This commit is contained in:
Kingkor Roy Tirtho 2025-01-12 21:32:33 +06:00
parent 3649b67869
commit 5930c342b5
11 changed files with 132 additions and 354 deletions

View File

@ -20,17 +20,6 @@ class $AssetsBackgroundsGen {
List<AssetGenImage> 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<AssetGenImage> 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 =

View File

@ -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>(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<MouseStateBuilder> {
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),
),
);
}
}

View File

@ -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<int>(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);
}

View File

@ -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<bool?>(null);
const type = ThemeType.auto;
final scale = context.theme.scaling;
Future<void> 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,
),
],
);
}
}

View File

@ -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),
);
}
}

View File

@ -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!();
},
);
}
}

View File

@ -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;
},

View File

@ -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,

View File

@ -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(

View File

@ -151,6 +151,8 @@ class LyricsPage extends HookConsumerWidget {
? TitleBar(
backgroundColor: Colors.transparent,
title: tabbar,
height: 58 * context.theme.scaling,
surfaceBlur: 0,
)
: tabbar
],

View File

@ -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()