mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-04-23 22:59:36 +00:00
非移动端修改为左侧导航
This commit is contained in:
parent
87d15c71d1
commit
0b71777b41
5 changed files with 213 additions and 73 deletions
|
|
@ -235,3 +235,57 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
|
||||||
|
|
||||||
// ! TODO remove onTap
|
// ! TODO remove onTap
|
||||||
void onTap() {}
|
void onTap() {}
|
||||||
|
|
||||||
|
class ProportionalAdjuster {
|
||||||
|
final double minValue;
|
||||||
|
final double maxValue;
|
||||||
|
|
||||||
|
ProportionalAdjuster({this.minValue = 0.0, this.maxValue = 1.0});
|
||||||
|
|
||||||
|
double adjust(double value, double ratio, {AdjustMethod method = AdjustMethod.linear}) {
|
||||||
|
switch (method) {
|
||||||
|
case AdjustMethod.linear:
|
||||||
|
return _linearAdjust(value, ratio);
|
||||||
|
case AdjustMethod.exponential:
|
||||||
|
return _exponentialAdjust(value, ratio);
|
||||||
|
case AdjustMethod.smooth:
|
||||||
|
return _smoothAdjust(value, ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _linearAdjust(double value, double ratio) {
|
||||||
|
if (value <= minValue) return minValue;
|
||||||
|
if (value >= maxValue) return maxValue;
|
||||||
|
|
||||||
|
double newValue;
|
||||||
|
if (ratio > 1) {
|
||||||
|
newValue = value + (maxValue - value) * (ratio - 1);
|
||||||
|
} else {
|
||||||
|
newValue = value * ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue.clamp(minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _exponentialAdjust(double value, double ratio) {
|
||||||
|
if (value <= minValue) return minValue;
|
||||||
|
if (value >= maxValue) return maxValue;
|
||||||
|
|
||||||
|
double newValue;
|
||||||
|
if (ratio > 1) {
|
||||||
|
newValue = maxValue - (maxValue - value) / ratio;
|
||||||
|
} else {
|
||||||
|
newValue = value * ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValue.clamp(minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _smoothAdjust(double value, double progress) {
|
||||||
|
progress = progress.clamp(0.0, 1.0);
|
||||||
|
double target = value > (minValue + maxValue) / 2 ? maxValue : minValue;
|
||||||
|
return value + (target - value) * progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AdjustMethod { linear, exponential, smooth }
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,16 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
earlyEnd,
|
earlyEnd,
|
||||||
)
|
)
|
||||||
.clamp(0.0, 1.0);
|
.clamp(0.0, 1.0);
|
||||||
|
|
||||||
final currentChapter = ref.watch(currentPlayingChapterProvider);
|
final currentChapter = ref.watch(currentPlayingChapterProvider);
|
||||||
final currentBookMetadata = ref.watch(currentBookMetadataProvider);
|
final currentBookMetadata = ref.watch(currentBookMetadataProvider);
|
||||||
|
|
||||||
|
final adjuster = ProportionalAdjuster();
|
||||||
|
final chapterPercentage = adjuster.adjust(earlyPercentage, 1.1);
|
||||||
|
final authorPercentage = adjuster.adjust(earlyPercentage, 1.2);
|
||||||
|
final progressPercentage = adjuster.adjust(earlyPercentage, 1.4);
|
||||||
|
final playPercentage = adjuster.adjust(earlyPercentage, 1.6);
|
||||||
|
final settingsPercentage = adjuster.adjust(earlyPercentage, 1.8);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// sized box for system status bar; not needed as not full screen
|
// sized box for system status bar; not needed as not full screen
|
||||||
|
|
@ -128,9 +135,9 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
|
|
||||||
// the chapter title
|
// the chapter title
|
||||||
if (earlyPercentage > 0.4)
|
if (chapterPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: earlyPercentage,
|
opacity: chapterPercentage,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
top: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
||||||
|
|
@ -152,9 +159,9 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
|
|
||||||
// the book name and author
|
// the book name and author
|
||||||
if (earlyPercentage > 0.5)
|
if (authorPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: earlyPercentage,
|
opacity: authorPercentage,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: AppElementSizes.paddingRegular * earlyPercentage,
|
bottom: AppElementSizes.paddingRegular * earlyPercentage,
|
||||||
|
|
@ -177,17 +184,17 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
// ),
|
// ),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (authorPercentage >= 1) const Spacer(),
|
||||||
|
|
||||||
if (earlyPercentage > 0.5) const Spacer(),
|
|
||||||
// the progress bar
|
// the progress bar
|
||||||
if (earlyPercentage > 0.6)
|
if (progressPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: earlyPercentage,
|
opacity: progressPercentage,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: imageSize,
|
width: imageSize,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
top: AppElementSizes.paddingRegular * earlyPercentage,
|
// top: AppElementSizes.paddingRegular * earlyPercentage,
|
||||||
left: AppElementSizes.paddingRegular * earlyPercentage,
|
left: AppElementSizes.paddingRegular * earlyPercentage,
|
||||||
right: AppElementSizes.paddingRegular * earlyPercentage,
|
right: AppElementSizes.paddingRegular * earlyPercentage,
|
||||||
),
|
),
|
||||||
|
|
@ -195,12 +202,12 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (earlyPercentage > 0.6) const Spacer(),
|
if (progressPercentage >= 1) const Spacer(),
|
||||||
|
|
||||||
// the chapter skip buttons, seek 30 seconds back and forward, and play/pause button
|
// the chapter skip buttons, seek 30 seconds back and forward, and play/pause button
|
||||||
if (earlyPercentage > 0.8)
|
if (playPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: earlyPercentage,
|
opacity: playPercentage,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: imageSize,
|
width: imageSize,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -221,23 +228,26 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (earlyPercentage > 0.8) const Spacer(),
|
if (playPercentage >= 1) const Spacer(),
|
||||||
|
|
||||||
// speed control, sleep timer, chapter list, and settings
|
// speed control, sleep timer, chapter list, and settings
|
||||||
if (earlyPercentage > 0.9)
|
if (settingsPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: earlyPercentage,
|
opacity: settingsPercentage,
|
||||||
child: Padding(
|
child: SizedBox(
|
||||||
padding: EdgeInsets.only(
|
// padding: EdgeInsets.only(
|
||||||
bottom: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
// bottom: AppElementSizes.paddingRegular * 4 * earlyPercentage,
|
||||||
),
|
// ),
|
||||||
|
width: imageSize,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
// speed control
|
// speed control
|
||||||
const PlayerSpeedAdjustButton(),
|
const PlayerSpeedAdjustButton(),
|
||||||
|
const Spacer(),
|
||||||
// sleep timer
|
// sleep timer
|
||||||
const SleepTimerButton(),
|
const SleepTimerButton(),
|
||||||
|
const Spacer(),
|
||||||
// chapter list
|
// chapter list
|
||||||
const ChapterSelectionButton(),
|
const ChapterSelectionButton(),
|
||||||
// settings
|
// settings
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,12 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
|
final bookMetaExpanded = ref.watch(currentBookMetadataProvider);
|
||||||
|
|
||||||
var barHeight = vanishingPercentage * 3;
|
var barHeight = vanishingPercentage * 3;
|
||||||
|
|
||||||
|
final adjuster = ProportionalAdjuster();
|
||||||
|
final rewindPercentage = adjuster.adjust(vanishingPercentage, 1.5);
|
||||||
|
final playPercentage = adjuster.adjust(vanishingPercentage, 1.8);
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -100,11 +104,10 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
// controller.animateToHeight(state: PanelState.MAX);
|
// controller.animateToHeight(state: PanelState.MAX);
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
|
|
||||||
// rewind button
|
// rewind button
|
||||||
if (vanishingPercentage > 0.6)
|
if (rewindPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: vanishingPercentage,
|
opacity: rewindPercentage,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 8),
|
padding: const EdgeInsets.only(left: 8),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
|
@ -117,9 +120,9 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// play/pause button
|
// play/pause button
|
||||||
if (vanishingPercentage > 0.8)
|
if (playPercentage >= 1)
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: vanishingPercentage,
|
opacity: playPercentage,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: AudiobookPlayerPlayPauseButton(
|
child: AudiobookPlayerPlayPauseButton(
|
||||||
|
|
@ -129,6 +132,7 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: barHeight,
|
height: barHeight,
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
@ -33,16 +35,8 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
// playerExpandProgress is used to animate bottom navigation bar to opacity 0 and slide down when player is expanded
|
|
||||||
// final playerProgress =
|
|
||||||
// useValueListenable(ref.watch(playerExpandProgressNotifierProvider));
|
|
||||||
final playerProgress = ref.watch(playerHeightProvider);
|
final playerProgress = ref.watch(playerHeightProvider);
|
||||||
final playerMaxHeight = MediaQuery.of(context).size.height;
|
final isMobile = Platform.isAndroid || Platform.isIOS;
|
||||||
var percentExpandedMiniPlayer = (playerProgress - playerMinHeight) /
|
|
||||||
(playerMaxHeight - playerMinHeight);
|
|
||||||
// Clamp the value between 0 and 1
|
|
||||||
percentExpandedMiniPlayer = percentExpandedMiniPlayer.clamp(0.0, 1.0);
|
|
||||||
|
|
||||||
onBackButtonPressed() async {
|
onBackButtonPressed() async {
|
||||||
final isPlayerExpanded = playerProgress != playerMinHeight;
|
final isPlayerExpanded = playerProgress != playerMinHeight;
|
||||||
|
|
||||||
|
|
@ -96,61 +90,129 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
|
||||||
return BackButtonListener(
|
return BackButtonListener(
|
||||||
onBackButtonPressed: onBackButtonPressed,
|
onBackButtonPressed: onBackButtonPressed,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Stack(
|
body: isMobile
|
||||||
children: [
|
? Stack(
|
||||||
navigationShell,
|
children: [
|
||||||
const AudiobookPlayer(),
|
navigationShell,
|
||||||
],
|
const AudiobookPlayer(),
|
||||||
),
|
],
|
||||||
bottomNavigationBar: Opacity(
|
)
|
||||||
// Opacity is interpolated from 1 to 0 when player is expanded
|
: buildNavLeft(context, ref),
|
||||||
opacity: 1 - percentExpandedMiniPlayer,
|
bottomNavigationBar: isMobile ? buildNavBottom(context, ref) : null,
|
||||||
child: NavigationBar(
|
),
|
||||||
elevation: 0.0,
|
);
|
||||||
height: bottomBarHeight * (1 - percentExpandedMiniPlayer),
|
}
|
||||||
|
|
||||||
// TODO: get destinations from the navigationShell
|
Widget buildNavLeft(BuildContext context, WidgetRef ref) {
|
||||||
// Here, the items of BottomNavigationBar are hard coded. In a real
|
return Row(
|
||||||
// world scenario, the items would most likely be generated from the
|
children: [
|
||||||
// branches of the shell route, which can be fetched using
|
SafeArea(
|
||||||
// `navigationShell.route.branches`.
|
child: NavigationRail(
|
||||||
|
minWidth: 60,
|
||||||
|
minExtendedWidth: 120,
|
||||||
|
extended: MediaQuery.of(context).size.width > 640,
|
||||||
|
// extended: false,
|
||||||
destinations: _navigationItems.map((item) {
|
destinations: _navigationItems.map((item) {
|
||||||
final isDestinationLibrary = item.name == 'Library';
|
final isDestinationLibrary = item.name == 'Library';
|
||||||
var currentLibrary =
|
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull;
|
||||||
ref.watch(currentLibraryProvider).valueOrNull;
|
|
||||||
final libraryIcon = AbsIcons.getIconByName(
|
final libraryIcon = AbsIcons.getIconByName(
|
||||||
currentLibrary?.icon,
|
currentLibrary?.icon,
|
||||||
);
|
);
|
||||||
final destinationWidget = NavigationDestination(
|
final destinationWidget = NavigationRailDestination(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
|
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
|
||||||
),
|
),
|
||||||
selectedIcon: Icon(
|
selectedIcon: Icon(
|
||||||
isDestinationLibrary
|
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon,
|
||||||
? libraryIcon ?? item.activeIcon
|
|
||||||
: item.activeIcon,
|
|
||||||
),
|
),
|
||||||
label: isDestinationLibrary
|
label: Text(isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name),
|
||||||
? currentLibrary?.name ?? item.name
|
// tooltip: item.tooltip,
|
||||||
: item.name,
|
|
||||||
tooltip: item.tooltip,
|
|
||||||
);
|
);
|
||||||
if (isDestinationLibrary) {
|
// if (isDestinationLibrary) {
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onSecondaryTap: () => showLibrarySwitcher(context, ref),
|
// onSecondaryTap: () => showLibrarySwitcher(context, ref),
|
||||||
onDoubleTap: () => showLibrarySwitcher(context, ref),
|
// onDoubleTap: () => showLibrarySwitcher(context, ref),
|
||||||
child:
|
// child:
|
||||||
destinationWidget, // Wrap the actual NavigationDestination
|
// destinationWidget, // Wrap the actual NavigationDestination
|
||||||
);
|
// );
|
||||||
} else {
|
// } else {
|
||||||
// Return the unwrapped destination for other items
|
// // Return the unwrapped destination for other items
|
||||||
return destinationWidget;
|
// return destinationWidget;
|
||||||
}
|
// }
|
||||||
|
return destinationWidget;
|
||||||
|
// return NavigationRailDestination(icon: Icon(nav.icon), label: Text(nav.name));
|
||||||
}).toList(),
|
}).toList(),
|
||||||
selectedIndex: navigationShell.currentIndex,
|
selectedIndex: navigationShell.currentIndex,
|
||||||
onDestinationSelected: (int index) => _onTap(context, index, ref),
|
onDestinationSelected: (int index) {
|
||||||
|
print(index);
|
||||||
|
_onTap(context, index, ref);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
VerticalDivider(width: 0.5, thickness: 0.5),
|
||||||
|
Expanded(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
navigationShell,
|
||||||
|
const AudiobookPlayer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildNavBottom(BuildContext context, WidgetRef ref) {
|
||||||
|
// playerExpandProgress is used to animate bottom navigation bar to opacity 0 and slide down when player is expanded
|
||||||
|
// final playerProgress =
|
||||||
|
// useValueListenable(ref.watch(playerExpandProgressNotifierProvider));
|
||||||
|
final playerProgress = ref.watch(playerHeightProvider);
|
||||||
|
final playerMaxHeight = MediaQuery.of(context).size.height;
|
||||||
|
var percentExpandedMiniPlayer =
|
||||||
|
(playerProgress - playerMinHeight) / (playerMaxHeight - playerMinHeight);
|
||||||
|
// Clamp the value between 0 and 1
|
||||||
|
percentExpandedMiniPlayer = percentExpandedMiniPlayer.clamp(0.0, 1.0);
|
||||||
|
return Opacity(
|
||||||
|
// Opacity is interpolated from 1 to 0 when player is expanded
|
||||||
|
opacity: 1 - percentExpandedMiniPlayer,
|
||||||
|
child: NavigationBar(
|
||||||
|
elevation: 0.0,
|
||||||
|
height: bottomBarHeight * (1 - percentExpandedMiniPlayer),
|
||||||
|
|
||||||
|
// TODO: get destinations from the navigationShell
|
||||||
|
// Here, the items of BottomNavigationBar are hard coded. In a real
|
||||||
|
// world scenario, the items would most likely be generated from the
|
||||||
|
// branches of the shell route, which can be fetched using
|
||||||
|
// `navigationShell.route.branches`.
|
||||||
|
destinations: _navigationItems.map((item) {
|
||||||
|
final isDestinationLibrary = item.name == 'Library';
|
||||||
|
var currentLibrary = ref.watch(currentLibraryProvider).valueOrNull;
|
||||||
|
final libraryIcon = AbsIcons.getIconByName(
|
||||||
|
currentLibrary?.icon,
|
||||||
|
);
|
||||||
|
final destinationWidget = NavigationDestination(
|
||||||
|
icon: Icon(
|
||||||
|
isDestinationLibrary ? libraryIcon ?? item.icon : item.icon,
|
||||||
|
),
|
||||||
|
selectedIcon: Icon(
|
||||||
|
isDestinationLibrary ? libraryIcon ?? item.activeIcon : item.activeIcon,
|
||||||
|
),
|
||||||
|
label: isDestinationLibrary ? currentLibrary?.name ?? item.name : item.name,
|
||||||
|
tooltip: item.tooltip,
|
||||||
|
);
|
||||||
|
if (isDestinationLibrary) {
|
||||||
|
return GestureDetector(
|
||||||
|
onSecondaryTap: () => showLibrarySwitcher(context, ref),
|
||||||
|
onDoubleTap: () => showLibrarySwitcher(context, ref),
|
||||||
|
child: destinationWidget, // Wrap the actual NavigationDestination
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Return the unwrapped destination for other items
|
||||||
|
return destinationWidget;
|
||||||
|
}
|
||||||
|
}).toList(),
|
||||||
|
selectedIndex: navigationShell.currentIndex,
|
||||||
|
onDestinationSelected: (int index) => _onTap(context, index, ref),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,14 @@ class AppDelegate: FlutterAppDelegate {
|
||||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
|
guard let window = NSApplication.shared.windows.first else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
window.setContentSize(NSSize(width: 1280, height: 720)) // 设置初始大小
|
||||||
|
// window.minSize = NSSize(width: 640, height: 480) // 最小尺寸
|
||||||
|
// window?.maxSize = NSSize(width: 1200, height: 900) // 最大尺寸(可选)
|
||||||
|
window.setFrameOrigin(NSPoint(x: 50, y: NSScreen.main?.frame.height ?? 1080 - (window.frame.height - 50)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue