mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
[flutter]: config popup window
This commit is contained in:
parent
ce5cccd670
commit
7a214ba3f4
@ -36,11 +36,10 @@ class ApplicationWidget extends StatelessWidget {
|
||||
// setWindowFrame(const Rect.fromLTWH(0, 0, launchWidth, launchWidth / ratio));
|
||||
|
||||
final theme = AppTheme.fromType(ThemeType.light);
|
||||
FlowyOverlayConfig config = FlowyOverlayConfig(barrierColor: Colors.transparent);
|
||||
return Provider.value(
|
||||
value: theme,
|
||||
child: MaterialApp(
|
||||
builder: overlayManagerBuilder(config: config),
|
||||
builder: overlayManagerBuilder(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: theme.themeData,
|
||||
navigatorKey: AppGlobals.rootNavKey,
|
||||
|
@ -0,0 +1,77 @@
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_edit.dart';
|
||||
|
||||
class ViewActionList implements FlowyOverlayDelegate {
|
||||
final Function(dartz.Option<ViewAction>) onSelected;
|
||||
final BuildContext anchorContext;
|
||||
final String _identifier = 'ViewActionList';
|
||||
|
||||
const ViewActionList({required this.anchorContext, required this.onSelected});
|
||||
|
||||
void show(BuildContext buildContext) {
|
||||
final items = ViewAction.values
|
||||
.map((action) => ActionItem(
|
||||
action: action,
|
||||
onSelected: (action) {
|
||||
FlowyOverlay.of(buildContext).remove(_identifier);
|
||||
onSelected(dartz.some(action));
|
||||
}))
|
||||
.toList();
|
||||
|
||||
ListOverlay.showWithAnchor(
|
||||
buildContext,
|
||||
identifier: _identifier,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) => items[index],
|
||||
anchorContext: anchorContext,
|
||||
anchorDirection: AnchorDirection.bottomRight,
|
||||
maxWidth: 120,
|
||||
maxHeight: 80,
|
||||
delegate: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didRemove() {
|
||||
onSelected(dartz.none());
|
||||
}
|
||||
}
|
||||
|
||||
class ActionItem extends StatelessWidget {
|
||||
final ViewAction action;
|
||||
final Function(ViewAction) onSelected;
|
||||
const ActionItem({
|
||||
Key? key,
|
||||
required this.action,
|
||||
required this.onSelected,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
return FlowyHover(
|
||||
config: HoverDisplayConfig(hoverColor: theme.hover),
|
||||
builder: (context, onHover) {
|
||||
return GestureDetector(
|
||||
onTap: () => onSelected(action),
|
||||
child: FlowyText.medium(
|
||||
action.name,
|
||||
fontSize: 12,
|
||||
).padding(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@ import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_ext.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_window.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@ -20,6 +20,8 @@ import 'package:app_flowy/workspace/domain/image.dart';
|
||||
import 'package:app_flowy/workspace/domain/view_edit.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/menu_app.dart';
|
||||
|
||||
import 'action.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ViewSectionItem extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
@ -89,8 +91,12 @@ class ViewSectionItem extends StatelessWidget {
|
||||
action.foldRight({}, (action, previous) {
|
||||
switch (action) {
|
||||
case ViewAction.rename:
|
||||
|
||||
// TODO: Handle this case.
|
||||
FlowyPoppuWindow.show(
|
||||
context,
|
||||
child: ViewRenamePannel(renameCallback: (name) {
|
||||
context.read<ViewBloc>().add(ViewEvent.rename(name));
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case ViewAction.delete:
|
||||
context.read<ViewBloc>().add(const ViewEvent.delete());
|
||||
@ -128,69 +134,12 @@ class ViewDisclosureButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ViewActionList implements FlowyOverlayDelegate {
|
||||
final Function(dartz.Option<ViewAction>) onSelected;
|
||||
final BuildContext anchorContext;
|
||||
final String _identifier = 'ViewActionList';
|
||||
|
||||
const ViewActionList({required this.anchorContext, required this.onSelected});
|
||||
|
||||
void show(BuildContext buildContext) {
|
||||
final items = ViewAction.values
|
||||
.map((action) => ActionItem(
|
||||
action: action,
|
||||
onSelected: (action) {
|
||||
FlowyOverlay.of(buildContext).remove(_identifier);
|
||||
onSelected(dartz.some(action));
|
||||
}))
|
||||
.toList();
|
||||
|
||||
ListOverlay.showWithAnchor(
|
||||
buildContext,
|
||||
identifier: _identifier,
|
||||
itemCount: items.length,
|
||||
itemBuilder: (context, index) => items[index],
|
||||
anchorContext: anchorContext,
|
||||
anchorDirection: AnchorDirection.bottomRight,
|
||||
maxWidth: 120,
|
||||
maxHeight: 80,
|
||||
delegate: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didRemove() {
|
||||
onSelected(dartz.none());
|
||||
}
|
||||
}
|
||||
|
||||
class ActionItem extends StatelessWidget {
|
||||
final ViewAction action;
|
||||
final Function(ViewAction) onSelected;
|
||||
const ActionItem({
|
||||
Key? key,
|
||||
required this.action,
|
||||
required this.onSelected,
|
||||
}) : super(key: key);
|
||||
class ViewRenamePannel extends StatelessWidget {
|
||||
final void Function(String) renameCallback;
|
||||
const ViewRenamePannel({Key? key, required this.renameCallback}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
return FlowyHover(
|
||||
config: HoverDisplayConfig(hoverColor: theme.hover),
|
||||
builder: (context, onHover) {
|
||||
return GestureDetector(
|
||||
onTap: () => onSelected(action),
|
||||
child: FlowyText.medium(
|
||||
action.name,
|
||||
fontSize: 12,
|
||||
).padding(
|
||||
horizontal: 10,
|
||||
vertical: 6,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
return SizedBox(width: 100, height: 200, child: Container(color: Colors.black));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'item.dart';
|
||||
|
||||
class ViewListNotifier extends ChangeNotifier {
|
||||
@ -47,10 +48,7 @@ class ViewSection extends StatelessWidget {
|
||||
// The ViewListNotifier will be updated after ViewListData changed passed by parent widget
|
||||
return ChangeNotifierProxyProvider<ViewListNotifier, ViewSectionNotifier>(
|
||||
create: (_) {
|
||||
final views = Provider.of<ViewListNotifier>(
|
||||
context,
|
||||
listen: false,
|
||||
).items;
|
||||
final views = Provider.of<ViewListNotifier>(context, listen: false).items;
|
||||
return ViewSectionNotifier(views);
|
||||
},
|
||||
update: (_, notifier, controller) => controller!..update(notifier),
|
||||
@ -61,20 +59,15 @@ class ViewSection extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _renderSectionItems(BuildContext context, List<View> views) {
|
||||
var viewWidgets = views.map((view) {
|
||||
final item = ViewSectionItem(
|
||||
var viewWidgets = views.map(
|
||||
(view) => ViewSectionItem(
|
||||
view: view,
|
||||
isSelected: _isViewSelected(context, view.id),
|
||||
onSelected: (view) => context.read<ViewSectionNotifier>().setSelectedView(view),
|
||||
);
|
||||
).padding(vertical: 4),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: item,
|
||||
);
|
||||
}).toList(growable: false);
|
||||
|
||||
return Column(children: viewWidgets);
|
||||
return Column(children: viewWidgets.toList(growable: false));
|
||||
}
|
||||
|
||||
bool _isViewSelected(BuildContext context, String viewId) {
|
||||
|
@ -0,0 +1,28 @@
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_size/window_size.dart';
|
||||
|
||||
class FlowyPoppuWindow extends StatelessWidget {
|
||||
final Widget child;
|
||||
const FlowyPoppuWindow({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return child;
|
||||
}
|
||||
|
||||
static Future<void> show(
|
||||
BuildContext context, {
|
||||
required Widget child,
|
||||
}) async {
|
||||
final window = await getWindowInfo();
|
||||
FlowyOverlay.of(context).insertWithRect(
|
||||
widget: FlowyPoppuWindow(child: child),
|
||||
identifier: 'FlowyPoppuWindow',
|
||||
anchorPosition: Offset.zero,
|
||||
anchorSize: window.frame.size,
|
||||
anchorDirection: AnchorDirection.center,
|
||||
style: FlowyOverlayStyle(blur: true),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
import 'package:dartz/dartz.dart' show Tuple3;
|
||||
import 'package:flowy_infra_ui/src/flowy_overlay/layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
/// Specifies how overlay are anchored to the SourceWidget
|
||||
enum AnchorDirection {
|
||||
@ -11,6 +12,7 @@ enum AnchorDirection {
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
center,
|
||||
|
||||
// Edge aligned with a edge of the SourceWidget
|
||||
topWithLeftAligned,
|
||||
@ -55,21 +57,20 @@ enum OnBackBehavior {
|
||||
dismiss,
|
||||
}
|
||||
|
||||
class FlowyOverlayConfig {
|
||||
class FlowyOverlayStyle {
|
||||
final Color barrierColor;
|
||||
bool blur;
|
||||
|
||||
FlowyOverlayConfig({required this.barrierColor});
|
||||
|
||||
const FlowyOverlayConfig.defualt() : barrierColor = Colors.transparent;
|
||||
FlowyOverlayStyle({this.barrierColor = Colors.transparent, this.blur = false});
|
||||
}
|
||||
|
||||
final GlobalKey<FlowyOverlayState> _key = GlobalKey<FlowyOverlayState>();
|
||||
|
||||
/// Invoke this method in app generation process
|
||||
TransitionBuilder overlayManagerBuilder({FlowyOverlayConfig config = const FlowyOverlayConfig.defualt()}) {
|
||||
TransitionBuilder overlayManagerBuilder() {
|
||||
return (context, child) {
|
||||
assert(child != null, 'Child can\'t be null.');
|
||||
return FlowyOverlay(key: _key, child: child!, config: config);
|
||||
return FlowyOverlay(key: _key, child: child!);
|
||||
};
|
||||
}
|
||||
|
||||
@ -78,12 +79,10 @@ abstract class FlowyOverlayDelegate {
|
||||
}
|
||||
|
||||
class FlowyOverlay extends StatefulWidget {
|
||||
const FlowyOverlay({Key? key, required this.child, required this.config}) : super(key: key);
|
||||
const FlowyOverlay({Key? key, required this.child}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
|
||||
final FlowyOverlayConfig config;
|
||||
|
||||
static FlowyOverlayState of(BuildContext context, {bool rootOverlay = false}) {
|
||||
FlowyOverlayState? state = maybeOf(context, rootOverlay: rootOverlay);
|
||||
assert(() {
|
||||
@ -113,6 +112,7 @@ class FlowyOverlay extends StatefulWidget {
|
||||
|
||||
class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
List<Tuple3<Widget, String, FlowyOverlayDelegate?>> _overlayList = [];
|
||||
FlowyOverlayStyle style = FlowyOverlayStyle();
|
||||
|
||||
/// Insert a overlay widget which frame is set by the widget, not the component.
|
||||
/// Be sure to specify the offset and size using a anchorable widget (like `Postition`, `CompositedTransformFollower`)
|
||||
@ -137,7 +137,12 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
AnchorDirection? anchorDirection,
|
||||
FlowyOverlayDelegate? delegate,
|
||||
OverlapBehaviour? overlapBehaviour,
|
||||
FlowyOverlayStyle? style,
|
||||
}) {
|
||||
if (style != null) {
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
_showOverlay(
|
||||
widget: widget,
|
||||
identifier: identifier,
|
||||
@ -157,7 +162,10 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
AnchorDirection? anchorDirection,
|
||||
FlowyOverlayDelegate? delegate,
|
||||
OverlapBehaviour? overlapBehaviour,
|
||||
FlowyOverlayStyle? style,
|
||||
}) {
|
||||
this.style = style ?? FlowyOverlayStyle();
|
||||
|
||||
_showOverlay(
|
||||
widget: widget,
|
||||
identifier: identifier,
|
||||
@ -245,17 +253,30 @@ class FlowyOverlayState extends State<FlowyOverlay> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final overlays = _overlayList.map((ele) => ele.value1);
|
||||
final children = <Widget>[
|
||||
widget.child,
|
||||
if (overlays.isNotEmpty)
|
||||
Container(
|
||||
color: widget.config.barrierColor,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleTapOnBackground,
|
||||
),
|
||||
List<Widget> children = <Widget>[widget.child];
|
||||
|
||||
Widget? child;
|
||||
if (overlays.isNotEmpty) {
|
||||
child = Container(
|
||||
color: style.barrierColor,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _handleTapOnBackground,
|
||||
),
|
||||
];
|
||||
);
|
||||
|
||||
if (style.blur) {
|
||||
child = BackdropFilter(
|
||||
child: child,
|
||||
filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (child != null) {
|
||||
children.add(child);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: children..addAll(overlays),
|
||||
);
|
||||
|
@ -55,6 +55,12 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
constraints.maxHeight - anchorRect.bottom,
|
||||
));
|
||||
break;
|
||||
case AnchorDirection.center:
|
||||
childConstraints = BoxConstraints.loose(Size(
|
||||
constraints.maxWidth,
|
||||
constraints.maxHeight,
|
||||
));
|
||||
break;
|
||||
case AnchorDirection.topWithLeftAligned:
|
||||
childConstraints = BoxConstraints.loose(Size(
|
||||
constraints.maxWidth - anchorRect.left,
|
||||
@ -165,6 +171,9 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
anchorRect.bottom,
|
||||
);
|
||||
break;
|
||||
case AnchorDirection.center:
|
||||
position = anchorRect.center;
|
||||
break;
|
||||
case AnchorDirection.topWithLeftAligned:
|
||||
position = Offset(
|
||||
anchorRect.left,
|
||||
|
@ -55,6 +55,7 @@ class ListOverlay extends StatelessWidget {
|
||||
AnchorDirection? anchorDirection,
|
||||
FlowyOverlayDelegate? delegate,
|
||||
OverlapBehaviour? overlapBehaviour,
|
||||
FlowyOverlayStyle? style,
|
||||
}) {
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
widget: ListOverlay(
|
||||
@ -69,6 +70,7 @@ class ListOverlay extends StatelessWidget {
|
||||
anchorDirection: anchorDirection,
|
||||
delegate: delegate,
|
||||
overlapBehaviour: overlapBehaviour,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
|
||||
@ -86,6 +88,7 @@ class ListOverlay extends StatelessWidget {
|
||||
AnchorDirection? anchorDirection,
|
||||
FlowyOverlayDelegate? delegate,
|
||||
OverlapBehaviour? overlapBehaviour,
|
||||
FlowyOverlayStyle? style,
|
||||
}) {
|
||||
FlowyOverlay.of(context).insertWithRect(
|
||||
widget: ListOverlay(
|
||||
@ -101,6 +104,7 @@ class ListOverlay extends StatelessWidget {
|
||||
anchorDirection: anchorDirection,
|
||||
delegate: delegate,
|
||||
overlapBehaviour: overlapBehaviour,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user