feat: On hover of the Nav Bar, expand to show current icon tooltip text (#4976)
* feat: On hover of the Nav Bar, expand to show current icon tooltip text * animate menu transition
This commit is contained in:
parent
86e83c80f2
commit
dd9a4858d1
4 changed files with 314 additions and 198 deletions
|
|
@ -17,6 +17,7 @@ class NaviRailItem extends StatelessWidget {
|
|||
// #Pangea
|
||||
final Color? backgroundColor;
|
||||
final BorderRadius? borderRadius;
|
||||
final bool expanded;
|
||||
// Pangea#
|
||||
|
||||
const NaviRailItem({
|
||||
|
|
@ -29,6 +30,7 @@ class NaviRailItem extends StatelessWidget {
|
|||
// #Pangea
|
||||
this.backgroundColor,
|
||||
this.borderRadius,
|
||||
this.expanded = false,
|
||||
// Pangea#
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -52,91 +54,118 @@ class NaviRailItem extends StatelessWidget {
|
|||
// #Pangea
|
||||
// return SizedBox(
|
||||
// height: 72,
|
||||
return SizedBox(
|
||||
height: width - (isColumnMode ? 16.0 : 12.0),
|
||||
width: width,
|
||||
// width: FluffyThemes.navRailWidth,
|
||||
// Pangea#
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
left: 0,
|
||||
child: AnimatedContainer(
|
||||
width: isSelected
|
||||
? FluffyThemes.isColumnMode(context)
|
||||
? 8
|
||||
: 4
|
||||
: 0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(90),
|
||||
bottomRight: Radius.circular(90),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: AnimatedScale(
|
||||
scale: hovered ? 1.1 : 1.0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
// #Pangea
|
||||
// child: Material(
|
||||
// borderRadius: borderRadius,
|
||||
// color: isSelected
|
||||
// ? theme.colorScheme.primaryContainer
|
||||
// : theme.colorScheme.surfaceContainerHigh,
|
||||
child: UnreadRoomsBadge(
|
||||
filter: unreadBadgeFilter ?? (_) => false,
|
||||
badgePosition: BadgePosition.topEnd(
|
||||
top: 1,
|
||||
end: isColumnMode ? 8 : 4,
|
||||
),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: width - (isColumnMode ? 16.0 : 12.0),
|
||||
width: width,
|
||||
// width: FluffyThemes.navRailWidth,
|
||||
// Pangea#
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
left: 0,
|
||||
child: AnimatedContainer(
|
||||
width: isSelected
|
||||
? FluffyThemes.isColumnMode(context)
|
||||
? 8
|
||||
: 4
|
||||
: 0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ??
|
||||
(isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.colorScheme.surfaceContainerHigh),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: isColumnMode ? 16.0 : 12.0,
|
||||
vertical: isColumnMode ? 8.0 : 6.0,
|
||||
),
|
||||
// Pangea#
|
||||
child: Tooltip(
|
||||
message: toolTip,
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: onTap,
|
||||
// #Pangea
|
||||
child: icon,
|
||||
// child: unreadBadgeFilter == null
|
||||
// ? icon
|
||||
// : UnreadRoomsBadge(
|
||||
// filter: unreadBadgeFilter,
|
||||
// badgePosition: BadgePosition.topEnd(
|
||||
// top: -12,
|
||||
// end: -8,
|
||||
// ),
|
||||
// child: icon,
|
||||
// ),
|
||||
// Pangea#
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(90),
|
||||
bottomRight: Radius.circular(90),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: AnimatedScale(
|
||||
scale: hovered ? 1.1 : 1.0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
// #Pangea
|
||||
// child: Material(
|
||||
// borderRadius: borderRadius,
|
||||
// color: isSelected
|
||||
// ? theme.colorScheme.primaryContainer
|
||||
// : theme.colorScheme.surfaceContainerHigh,
|
||||
child: UnreadRoomsBadge(
|
||||
filter: unreadBadgeFilter ?? (_) => false,
|
||||
badgePosition: BadgePosition.topEnd(
|
||||
top: 1,
|
||||
end: isColumnMode ? 8 : 4,
|
||||
),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor ??
|
||||
(isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.colorScheme.surfaceContainerHigh),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: isColumnMode ? 16.0 : 12.0,
|
||||
vertical: isColumnMode ? 8.0 : 6.0,
|
||||
),
|
||||
// Pangea#
|
||||
child: Tooltip(
|
||||
message: toolTip,
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: onTap,
|
||||
// #Pangea
|
||||
child: icon,
|
||||
// child: unreadBadgeFilter == null
|
||||
// ? icon
|
||||
// : UnreadRoomsBadge(
|
||||
// filter: unreadBadgeFilter,
|
||||
// badgePosition: BadgePosition.topEnd(
|
||||
// top: -12,
|
||||
// end: -8,
|
||||
// ),
|
||||
// child: icon,
|
||||
// ),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (expanded)
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
toolTip,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
onTap: onTap,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 0.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
180
lib/pangea/spaces/space_navigation_column.dart
Normal file
180
lib/pangea/spaces/space_navigation_column.dart
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pages/settings/settings.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_page/activity_archive.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/level_analytics_details_content.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||
|
||||
class SpaceNavigationColumn extends StatefulWidget {
|
||||
final GoRouterState state;
|
||||
const SpaceNavigationColumn({
|
||||
required this.state,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SpaceNavigationColumn> createState() => SpaceNavigationColumnState();
|
||||
}
|
||||
|
||||
class SpaceNavigationColumnState extends State<SpaceNavigationColumn> {
|
||||
bool _expand = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
bool showNavRail = isColumnMode;
|
||||
if (!showNavRail) {
|
||||
final roomID = widget.state.pathParameters['roomid'];
|
||||
final spaceID = widget.state.pathParameters['spaceid'];
|
||||
|
||||
if (roomID == null && spaceID == null) {
|
||||
showNavRail = !["newcourse", ":construct"].any(
|
||||
(p) => widget.state.fullPath?.contains(p) ?? false,
|
||||
);
|
||||
} else if (roomID == null) {
|
||||
showNavRail = widget.state.fullPath?.endsWith(':spaceid') == true;
|
||||
}
|
||||
}
|
||||
|
||||
final navRailWidth = isColumnMode
|
||||
? FluffyThemes.navRailWidth
|
||||
: FluffyThemes.navRailWidth - 8.0;
|
||||
|
||||
final double navRailExtraWidth = showNavRail ? 250.0 : 0.0;
|
||||
final double columnWidth =
|
||||
isColumnMode ? FluffyThemes.columnWidth + 1.0 : 0;
|
||||
|
||||
final double railWidth = showNavRail ? navRailWidth + 1.0 : 0;
|
||||
final double baseWidth = columnWidth + railWidth;
|
||||
final double expandedWidth = baseWidth < navRailExtraWidth
|
||||
? navRailExtraWidth + railWidth
|
||||
: baseWidth;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
width: _expand ? expandedWidth : baseWidth,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (isColumnMode)
|
||||
Positioned(
|
||||
left: navRailWidth + 1.0,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.heightOf(context),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(),
|
||||
width: FluffyThemes.columnWidth,
|
||||
child: _MainView(state: widget.state),
|
||||
),
|
||||
Container(
|
||||
width: 1.0,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showNavRail)
|
||||
HoverBuilder(
|
||||
builder: (context, hovered) {
|
||||
if (_expand != hovered) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
setState(() {
|
||||
_expand = hovered;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: widget.state.pathParameters['spaceid'],
|
||||
path: widget.state.fullPath,
|
||||
railWidth: _expand
|
||||
? navRailWidth + navRailExtraWidth
|
||||
: navRailWidth,
|
||||
expanded: _expand,
|
||||
),
|
||||
Container(
|
||||
width: 1,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MainView extends StatelessWidget {
|
||||
final GoRouterState state;
|
||||
|
||||
const _MainView({
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final path = state.fullPath;
|
||||
if (path == null) {
|
||||
return ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
);
|
||||
}
|
||||
|
||||
if (path.contains("analytics")) {
|
||||
if (path.contains("analytics/level")) {
|
||||
return const LevelAnalyticsDetailsContent();
|
||||
} else if (path.contains("analytics/activities")) {
|
||||
return const ActivityArchive();
|
||||
} else if (path.contains("analytics/${ConstructTypeEnum.morph.string}")) {
|
||||
return const ConstructAnalyticsView(view: ConstructTypeEnum.morph);
|
||||
}
|
||||
return const ConstructAnalyticsView(view: ConstructTypeEnum.vocab);
|
||||
}
|
||||
|
||||
if (path.contains("settings")) {
|
||||
return Settings(key: state.pageKey);
|
||||
}
|
||||
|
||||
if (path.contains('course')) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 250.0,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${SpaceConstants.sideBearFileName}",
|
||||
errorWidget: (context, url, error) => const SizedBox(),
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pages/settings/settings.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/analytics_page/activity_archive.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/level_analytics_details_content.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_constants.dart';
|
||||
import 'package:fluffychat/widgets/navigation_rail.dart';
|
||||
import 'package:fluffychat/pangea/spaces/space_navigation_column.dart';
|
||||
|
||||
class TwoColumnLayout extends StatelessWidget {
|
||||
// #Pangea
|
||||
|
|
@ -31,123 +21,27 @@ class TwoColumnLayout extends StatelessWidget {
|
|||
});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
// #Pangea
|
||||
bool showNavRail = FluffyThemes.isColumnMode(context);
|
||||
if (!showNavRail) {
|
||||
final roomID = state.pathParameters['roomid'];
|
||||
final spaceID = state.pathParameters['spaceid'];
|
||||
|
||||
if (roomID == null && spaceID == null) {
|
||||
showNavRail = !["newcourse", ":construct"].any(
|
||||
(p) => state.fullPath?.contains(p) ?? false,
|
||||
);
|
||||
} else if (roomID == null) {
|
||||
showNavRail = state.fullPath?.endsWith(':spaceid') == true;
|
||||
}
|
||||
}
|
||||
// final theme = Theme.of(context);
|
||||
// Pangea#
|
||||
|
||||
return ScaffoldMessenger(
|
||||
child: Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
// #Pangea
|
||||
if (showNavRail) ...[
|
||||
SpacesNavigationRail(
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
path: state.fullPath,
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
],
|
||||
if (FluffyThemes.isColumnMode(context)) ...[
|
||||
// Pangea#
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: const BoxDecoration(),
|
||||
// #Pangea
|
||||
// width: FluffyThemes.columnWidth + FluffyThemes.navRailWidth,
|
||||
// child: mainView,
|
||||
width: FluffyThemes.columnWidth,
|
||||
child: _MainView(state: state),
|
||||
// Pangea#
|
||||
),
|
||||
Container(
|
||||
width: 1.0,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
SpaceNavigationColumn(state: state),
|
||||
// Container(
|
||||
// clipBehavior: Clip.antiAlias,
|
||||
// decoration: const BoxDecoration(),
|
||||
// width: FluffyThemes.columnWidth + FluffyThemes.navRailWidth,
|
||||
// child: mainView,
|
||||
// ),
|
||||
// Container(width: 1.0, color: theme.dividerColor),
|
||||
// Pangea#
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
child: sideView,
|
||||
),
|
||||
),
|
||||
Expanded(child: ClipRRect(child: sideView)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
class _MainView extends StatelessWidget {
|
||||
final GoRouterState state;
|
||||
|
||||
const _MainView({
|
||||
required this.state,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final path = state.fullPath;
|
||||
if (path == null) {
|
||||
return ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
);
|
||||
}
|
||||
|
||||
if (path.contains("analytics")) {
|
||||
if (path.contains("analytics/level")) {
|
||||
return const LevelAnalyticsDetailsContent();
|
||||
} else if (path.contains("analytics/activities")) {
|
||||
return const ActivityArchive();
|
||||
} else if (path.contains("analytics/${ConstructTypeEnum.morph.string}")) {
|
||||
return const ConstructAnalyticsView(view: ConstructTypeEnum.morph);
|
||||
}
|
||||
return const ConstructAnalyticsView(view: ConstructTypeEnum.vocab);
|
||||
}
|
||||
|
||||
if (path.contains("settings")) {
|
||||
return Settings(key: state.pageKey);
|
||||
}
|
||||
|
||||
if (path.contains('course')) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 250.0,
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
"${AppConfig.assetsBaseURL}/${SpaceConstants.sideBearFileName}",
|
||||
errorWidget: (context, url, error) => const SizedBox(),
|
||||
placeholder: (context, url) => const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ChatList(
|
||||
activeChat: state.pathParameters['roomid'],
|
||||
activeSpaceId: state.pathParameters['spaceid'],
|
||||
);
|
||||
}
|
||||
}
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
// final void Function() onGoToChats;
|
||||
// final void Function(String) onGoToSpaceId;
|
||||
final String? path;
|
||||
final double railWidth;
|
||||
final bool expanded;
|
||||
// Pangea#
|
||||
|
||||
const SpacesNavigationRail({
|
||||
|
|
@ -30,6 +32,8 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
// required this.onGoToChats,
|
||||
// required this.onGoToSpaceId,
|
||||
required this.path,
|
||||
required this.railWidth,
|
||||
this.expanded = false,
|
||||
// Pangea#
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -73,10 +77,12 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
)
|
||||
.toList();
|
||||
|
||||
return SizedBox(
|
||||
// #Pangea
|
||||
// #Pangea
|
||||
// return SizedBox(
|
||||
return AnimatedContainer(
|
||||
// width: FluffyThemes.navRailWidth,
|
||||
width: width,
|
||||
width: railWidth,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
// Pangea#
|
||||
child: Column(
|
||||
children: [
|
||||
|
|
@ -118,6 +124,9 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
toolTip: L10n.of(context).home,
|
||||
// #Pangea
|
||||
expanded: expanded,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
i--;
|
||||
|
|
@ -147,6 +156,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
toolTip: L10n.of(context).directMessages,
|
||||
unreadBadgeFilter: (room) =>
|
||||
room.firstSpaceParent == null,
|
||||
expanded: expanded,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
|
|
@ -183,6 +193,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
toolTip: L10n.of(context).addCourse,
|
||||
expanded: expanded,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
|
|
@ -260,6 +271,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
expanded: expanded,
|
||||
// Pangea#
|
||||
);
|
||||
},
|
||||
|
|
@ -279,6 +291,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
|||
// ),
|
||||
icon: const Icon(Icons.settings_outlined),
|
||||
selectedIcon: const Icon(Icons.settings),
|
||||
expanded: expanded,
|
||||
// Pangea#
|
||||
toolTip: L10n.of(context).settings,
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue