From 6d25568c59df4e67c164e93ac780b6c8e4ccaf31 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Fri, 24 Apr 2026 21:22:59 -0400 Subject: [PATCH] wip popovers --- lib/helpers/extensions/scheme_to_theme.dart | 4 + lib/main.dart | 5 +- lib/widgets/bar.dart | 110 +++++++++++--------- lib/widgets/bubble.dart | 58 +++++++++++ pubspec.lock | 10 +- pubspec.yaml | 2 + rust/src/internal/wayland.rs | 2 +- 7 files changed, 136 insertions(+), 55 deletions(-) create mode 100644 lib/widgets/bubble.dart diff --git a/lib/helpers/extensions/scheme_to_theme.dart b/lib/helpers/extensions/scheme_to_theme.dart index 91d41f0..84cf8c2 100644 --- a/lib/helpers/extensions/scheme_to_theme.dart +++ b/lib/helpers/extensions/scheme_to_theme.dart @@ -9,5 +9,9 @@ extension SchemeToTheme on ColorScheme { inputDecorationTheme: const InputDecorationTheme( border: OutlineInputBorder(), ), + popupMenuTheme: PopupMenuThemeData( + color: Colors.transparent, + shadowColor: Colors.transparent, + ), ); } diff --git a/lib/main.dart b/lib/main.dart index ddd6a8d..62f0198 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:flight/helpers/extensions/scheme_to_theme.dart'; import 'package:flight/src/rust/frb_generated.dart'; import 'package:flight/widgets/bar.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:wayland_layer_shell/types.dart'; import 'package:wayland_layer_shell/wayland_layer_shell.dart'; @@ -38,7 +39,9 @@ void main() async { brightness: Brightness.dark, )) .theme, - home: Scaffold(body: Bar(), backgroundColor: Colors.transparent), + home: Portal( + child: Scaffold(body: Bar(), backgroundColor: Colors.transparent), + ), ), ), ), diff --git a/lib/widgets/bar.dart b/lib/widgets/bar.dart index 04044c8..9334cc8 100644 --- a/lib/widgets/bar.dart +++ b/lib/widgets/bar.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flight/controllers/time_controller.dart'; import 'package:flight/controllers/workspaces_controller.dart'; +import 'package:flight/widgets/bubble.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; @@ -21,82 +22,87 @@ class Bar extends ConsumerWidget { loading: () => [SizedBox.shrink()], data: (value) => value .map( - (group) => Row( - children: group - .map( - (workspace) => IconButton( - onPressed: () => workspace.activate(), - icon: Icon( - workspace.activated - ? Icons.circle - : Icons.circle_outlined, + (group) => Bubble( + Row( + children: group + .map( + (workspace) => IconButton( + onPressed: () => workspace.activate(), + icon: Icon( + workspace.activated + ? Icons.circle + : Icons.circle_outlined, + ), ), - ), - ) - .toList(), + ) + .toList(), + ), ), ) .toList(), ), [ - TextButton( - onPressed: () {}, - child: Text( - DateFormat.Hm().format( - ref - .watch(TimeController.provider) - .when( - data: (time) => time, - loading: DateTime.now, - error: (_, _) => DateTime.now(), + Bubble( + popover: Bubble( + Padding( + padding: EdgeInsetsGeometry.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + DateFormat.MMMMEEEEd().format(DateTime.now()), + style: Theme.of(context).textTheme.headlineMedium, ), + ], + ), + ), + ), + Center( + child: Text( + DateFormat.Hm().format( + ref + .watch(TimeController.provider) + .when( + data: (time) => time, + loading: DateTime.now, + error: (_, _) => DateTime.now(), + ), + ), ), ), ), ], [ - Row( - children: [ - IconButton(onPressed: () {}, icon: Icon(Icons.wifi)), - IconButton(onPressed: () {}, icon: Icon(Icons.bluetooth)), + Bubble( + Row( + children: [ + IconButton(onPressed: () {}, icon: Icon(Icons.wifi)), + IconButton( + onPressed: () {}, + icon: Icon(Icons.bluetooth), + ), - IconButton( - onPressed: () {}, - icon: Icon(Icons.volume_off), - ), - ], + IconButton( + onPressed: () {}, + icon: Icon(Icons.volume_off), + ), + ], + ), ), ], ] .mapIndexed( (index, children) => Align( alignment: [ - Alignment.centerLeft, - Alignment.center, - Alignment.centerRight, + Alignment.bottomLeft, + Alignment.bottomCenter, + Alignment.bottomRight, ][index], child: Row( spacing: 8, mainAxisSize: MainAxisSize.min, - children: children - .map( - (child) => Padding( - padding: EdgeInsetsGeometry.directional(bottom: 6), - child: Container( - height: 42, - padding: EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - color: Theme.of( - context, - ).colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(999), - ), - child: child, - ), - ), - ) - .toList(), + children: children, ), ), ) diff --git a/lib/widgets/bubble.dart b/lib/widgets/bubble.dart new file mode 100644 index 0000000..dd089af --- /dev/null +++ b/lib/widgets/bubble.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_portal/flutter_portal.dart'; +import 'package:wayland_layer_shell/types.dart'; +import 'package:wayland_layer_shell/wayland_layer_shell.dart'; + +class Bubble extends HookWidget { + final Widget child; + final Widget? popover; + const Bubble(this.child, {this.popover, super.key}); + + @override + Widget build(BuildContext context) { + final isVisible = useState(false); + return PortalTarget( + visible: isVisible.value, + anchor: const Filled(), + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + isVisible.value = false; + WaylandLayerShell().setAnchor(ShellEdge.edgeTop, false); + }, + ), + child: PortalTarget( + visible: isVisible.value, + anchor: Aligned( + follower: Alignment.bottomCenter, + target: Alignment.topCenter, + ), + portalFollower: popover, + child: Padding( + padding: const EdgeInsets.only(bottom: 6), + child: Material( + color: Theme.of(context).colorScheme.surfaceContainerLow, + borderRadius: BorderRadius.circular(999), + child: InkWell( + borderRadius: BorderRadius.circular(999), + onTap: popover == null + ? null + : () async { + await WaylandLayerShell().setAnchor( + ShellEdge.edgeTop, + true, + ); + isVisible.value = true; + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: child, + ), + ), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index e1523b3..14af377 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -248,7 +248,7 @@ packages: source: sdk version: "0.0.0" flutter_hooks: - dependency: transitive + dependency: "direct main" description: name: flutter_hooks sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" @@ -268,6 +268,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_portal: + dependency: "direct main" + description: + name: flutter_portal + sha256: "4601b3dc24f385b3761721bd852a3f6c09cddd4e943dd184ed58ee1f43006257" + url: "https://pub.dev" + source: hosted + version: "1.1.4" flutter_riverpod: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 07eed38..d9f48de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: path: rust_builder flutter_rust_bridge: 2.11.1 collection: ^1.19.1 + flutter_hooks: ^0.21.3+1 + flutter_portal: ^1.1.4 dev_dependencies: build_runner: ^2.13.1 diff --git a/rust/src/internal/wayland.rs b/rust/src/internal/wayland.rs index 9816e2d..9771ce0 100644 --- a/rust/src/internal/wayland.rs +++ b/rust/src/internal/wayland.rs @@ -63,7 +63,7 @@ impl AppState { }) .collect(); - self.sink.add(result).expect("Updating stream failed"); + let _ = self.sink.add(result); } }