wip popovers

This commit is contained in:
Henry Hiles 2026-04-24 21:22:59 -04:00
commit 6d25568c59
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
7 changed files with 136 additions and 55 deletions

View file

@ -9,5 +9,9 @@ extension SchemeToTheme on ColorScheme {
inputDecorationTheme: const InputDecorationTheme( inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
popupMenuTheme: PopupMenuThemeData(
color: Colors.transparent,
shadowColor: Colors.transparent,
),
); );
} }

View file

@ -3,6 +3,7 @@ import 'package:flight/helpers/extensions/scheme_to_theme.dart';
import 'package:flight/src/rust/frb_generated.dart'; import 'package:flight/src/rust/frb_generated.dart';
import 'package:flight/widgets/bar.dart'; import 'package:flight/widgets/bar.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:wayland_layer_shell/types.dart'; import 'package:wayland_layer_shell/types.dart';
import 'package:wayland_layer_shell/wayland_layer_shell.dart'; import 'package:wayland_layer_shell/wayland_layer_shell.dart';
@ -38,7 +39,9 @@ void main() async {
brightness: Brightness.dark, brightness: Brightness.dark,
)) ))
.theme, .theme,
home: Scaffold(body: Bar(), backgroundColor: Colors.transparent), home: Portal(
child: Scaffold(body: Bar(), backgroundColor: Colors.transparent),
),
), ),
), ),
), ),

View file

@ -1,6 +1,7 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flight/controllers/time_controller.dart'; import 'package:flight/controllers/time_controller.dart';
import 'package:flight/controllers/workspaces_controller.dart'; import 'package:flight/controllers/workspaces_controller.dart';
import 'package:flight/widgets/bubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -21,82 +22,87 @@ class Bar extends ConsumerWidget {
loading: () => [SizedBox.shrink()], loading: () => [SizedBox.shrink()],
data: (value) => value data: (value) => value
.map( .map(
(group) => Row( (group) => Bubble(
children: group Row(
.map( children: group
(workspace) => IconButton( .map(
onPressed: () => workspace.activate(), (workspace) => IconButton(
icon: Icon( onPressed: () => workspace.activate(),
workspace.activated icon: Icon(
? Icons.circle workspace.activated
: Icons.circle_outlined, ? Icons.circle
: Icons.circle_outlined,
),
), ),
), )
) .toList(),
.toList(), ),
), ),
) )
.toList(), .toList(),
), ),
[ [
TextButton( Bubble(
onPressed: () {}, popover: Bubble(
child: Text( Padding(
DateFormat.Hm().format( padding: EdgeInsetsGeometry.all(8),
ref child: Column(
.watch(TimeController.provider) mainAxisSize: MainAxisSize.min,
.when( children: [
data: (time) => time, Text(
loading: DateTime.now, DateFormat.MMMMEEEEd().format(DateTime.now()),
error: (_, _) => 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( Bubble(
children: [ Row(
IconButton(onPressed: () {}, icon: Icon(Icons.wifi)), children: [
IconButton(onPressed: () {}, icon: Icon(Icons.bluetooth)), IconButton(onPressed: () {}, icon: Icon(Icons.wifi)),
IconButton(
onPressed: () {},
icon: Icon(Icons.bluetooth),
),
IconButton( IconButton(
onPressed: () {}, onPressed: () {},
icon: Icon(Icons.volume_off), icon: Icon(Icons.volume_off),
), ),
], ],
),
), ),
], ],
] ]
.mapIndexed( .mapIndexed(
(index, children) => Align( (index, children) => Align(
alignment: [ alignment: [
Alignment.centerLeft, Alignment.bottomLeft,
Alignment.center, Alignment.bottomCenter,
Alignment.centerRight, Alignment.bottomRight,
][index], ][index],
child: Row( child: Row(
spacing: 8, spacing: 8,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: children 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(),
), ),
), ),
) )

58
lib/widgets/bubble.dart Normal file
View file

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

View file

@ -248,7 +248,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_hooks: flutter_hooks:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_hooks name: flutter_hooks
sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42"
@ -268,6 +268,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: flutter_riverpod:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -25,6 +25,8 @@ dependencies:
path: rust_builder path: rust_builder
flutter_rust_bridge: 2.11.1 flutter_rust_bridge: 2.11.1
collection: ^1.19.1 collection: ^1.19.1
flutter_hooks: ^0.21.3+1
flutter_portal: ^1.1.4
dev_dependencies: dev_dependencies:
build_runner: ^2.13.1 build_runner: ^2.13.1

View file

@ -63,7 +63,7 @@ impl AppState {
}) })
.collect(); .collect();
self.sink.add(result).expect("Updating stream failed"); let _ = self.sink.add(result);
} }
} }