mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-07-23 17:11:23 +00:00
feat: customize animation for popover (#6507)
* feat: customize animation for popover * chore: code refactor * feat: using popover direction calculate the popover animation translate direction * feat: integrate the animated popover in appflowy_popover and popover_action * fix: close popover assertion * chore: format code * chore: code refactor * feat: optimize the popover listener * feat: clear popover when hot-reloading * chore: refactor code * fix: integration test * fix: icon test
This commit is contained in:
parent
580a23f3f5
commit
8cf683eb50
@ -11,7 +11,7 @@ void main() {
|
||||
|
||||
const emoji = '😁';
|
||||
|
||||
group('Icon', () {
|
||||
group('Icon:', () {
|
||||
testWidgets('Update page icon in sidebar', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
@ -52,6 +52,7 @@ void main() {
|
||||
if (value == ViewLayoutPB.Chat) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await tester.createNewPageWithNameUnderParent(
|
||||
name: value.name,
|
||||
parentName: gettingStarted,
|
||||
|
@ -3,7 +3,9 @@ import 'package:scaled_app/scaled_app.dart';
|
||||
import 'startup/startup.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(scaleFactor: (_) => 1.0);
|
||||
ScaledWidgetsFlutterBinding.ensureInitialized(
|
||||
scaleFactor: (_) => 1.0,
|
||||
);
|
||||
|
||||
await runAppFlowy();
|
||||
}
|
||||
|
@ -629,23 +629,23 @@ class FieldNameTextField extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
FocusNode focusNode = FocusNode();
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
focusNode.addListener(() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
focusNode.addListener(_onFocusChanged);
|
||||
widget.popoverMutex?.addPopoverListener(_onPopoverChanged);
|
||||
}
|
||||
|
||||
widget.popoverMutex?.listenOnPopoverChanged(() {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
});
|
||||
@override
|
||||
void dispose() {
|
||||
widget.popoverMutex?.removePopoverListener(_onPopoverChanged);
|
||||
focusNode.removeListener(_onFocusChanged);
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -662,15 +662,16 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.removeListener(() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
void _onFocusChanged() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPopoverChanged() {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,22 +206,23 @@ class CreateOptionTextField extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
||||
late final FocusNode _focusNode;
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_focusNode = FocusNode()
|
||||
..addListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
widget.popoverMutex?.listenOnPopoverChanged(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
_focusNode.unfocus();
|
||||
}
|
||||
});
|
||||
|
||||
focusNode.addListener(_onFocusChanged);
|
||||
widget.popoverMutex?.addPopoverListener(_onPopoverChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.popoverMutex?.removePopoverListener(_onPopoverChanged);
|
||||
focusNode.removeListener(_onFocusChanged);
|
||||
focusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -234,7 +235,7 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
||||
child: FlowyTextField(
|
||||
autoClearWhenDone: true,
|
||||
text: text,
|
||||
focusNode: _focusNode,
|
||||
focusNode: focusNode,
|
||||
onCanceled: () {
|
||||
context
|
||||
.read<SelectOptionTypeOptionBloc>()
|
||||
@ -252,15 +253,16 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focusNode.removeListener(() {
|
||||
if (_focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
void _onFocusChanged() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
}
|
||||
|
||||
void _onPopoverChanged() {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,12 +70,12 @@ class _BlockOptionButtonState extends State<BlockOptionButton> {
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverActionList<PopoverAction>(
|
||||
popoverMutex: PopoverMutex(),
|
||||
actions: popoverActions,
|
||||
direction:
|
||||
context.read<AppearanceSettingsCubit>().state.layoutDirection ==
|
||||
LayoutDirection.rtlLayout
|
||||
? PopoverDirection.rightWithCenterAligned
|
||||
: PopoverDirection.leftWithCenterAligned,
|
||||
actions: popoverActions,
|
||||
onPopupBuilder: () {
|
||||
keepEditorFocusNotifier.increase();
|
||||
widget.blockComponentState.alwaysShowActions = true;
|
||||
|
@ -1,9 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
const _maxLengthTwelveHour = 8;
|
||||
@ -64,7 +63,7 @@ class _TimeTextFieldState extends State<TimeTextField> {
|
||||
}
|
||||
|
||||
_focusNode.addListener(_focusNodeListener);
|
||||
widget.popoverMutex?.listenOnPopoverChanged(_popoverListener);
|
||||
widget.popoverMutex?.addPopoverListener(_popoverListener);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -17,6 +17,8 @@ class PopoverActionList<T extends PopoverAction> extends StatefulWidget {
|
||||
this.direction = PopoverDirection.rightWithTopAligned,
|
||||
this.asBarrier = false,
|
||||
this.offset = Offset.zero,
|
||||
this.animationDuration = const Duration(),
|
||||
this.slideDistance = 20,
|
||||
this.constraints = const BoxConstraints(
|
||||
minWidth: 120,
|
||||
maxWidth: 460,
|
||||
@ -35,6 +37,8 @@ class PopoverActionList<T extends PopoverAction> extends StatefulWidget {
|
||||
final bool asBarrier;
|
||||
final Offset offset;
|
||||
final BoxConstraints constraints;
|
||||
final Duration animationDuration;
|
||||
final double slideDistance;
|
||||
|
||||
@override
|
||||
State<PopoverActionList<T>> createState() => _PopoverActionListState<T>();
|
||||
@ -55,6 +59,8 @@ class _PopoverActionListState<T extends PopoverAction>
|
||||
final child = widget.buildChild(popoverController);
|
||||
return AppFlowyPopover(
|
||||
asBarrier: widget.asBarrier,
|
||||
animationDuration: widget.animationDuration,
|
||||
slideDistance: widget.slideDistance,
|
||||
controller: popoverController,
|
||||
constraints: widget.constraints,
|
||||
direction: widget.direction,
|
||||
|
@ -1,4 +1,60 @@
|
||||
# This file configures the analyzer, which statically analyzes Dart code to
|
||||
# check for errors, warnings, and lints.
|
||||
#
|
||||
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||
# invoked from the command line by running `flutter analyze`.
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
# included above or to enable additional rules. A list of all available lints
|
||||
# and their documentation is published at
|
||||
# https://dart-lang.github.io/linter/lints/index.html.
|
||||
#
|
||||
# Instead of disabling a lint rule for the entire project in the
|
||||
# section below, it can also be suppressed for a single line of code
|
||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
- require_trailing_commas
|
||||
|
||||
- prefer_collection_literals
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
||||
- sized_box_for_whitespace
|
||||
- use_decorated_box
|
||||
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_await_in_return
|
||||
- unnecessary_raw_strings
|
||||
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_unused_constructor_parameters
|
||||
|
||||
- always_declare_return_types
|
||||
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
- prefer_single_quotes
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
|
@ -7,8 +7,14 @@
|
||||
|
||||
# The following line activates a set of recommended lints for Flutter apps,
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "**/*.g.dart"
|
||||
- "**/*.freezed.dart"
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
@ -22,8 +28,33 @@ linter:
|
||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
- require_trailing_commas
|
||||
|
||||
- prefer_collection_literals
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
||||
- sized_box_for_whitespace
|
||||
- use_decorated_box
|
||||
|
||||
- unnecessary_parenthesis
|
||||
- unnecessary_await_in_return
|
||||
- unnecessary_raw_strings
|
||||
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_redundant_argument_values
|
||||
- avoid_unused_constructor_parameters
|
||||
|
||||
- always_declare_return_types
|
||||
|
||||
- sort_constructors_first
|
||||
- unawaited_futures
|
||||
|
||||
- prefer_single_quotes
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
errors:
|
||||
invalid_annotation_target: ignore
|
||||
|
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -127,7 +127,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@ -171,10 +171,12 @@
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
@ -185,6 +187,7 @@
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
@ -272,7 +275,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
@ -349,7 +352,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -398,7 +401,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -45,5 +45,7 @@
|
||||
<false/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PopoverMenu extends StatefulWidget {
|
||||
const PopoverMenu({super.key});
|
||||
@ -14,43 +14,32 @@ class _PopoverMenuState extends State<PopoverMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 5,
|
||||
blurRadius: 7,
|
||||
offset: const Offset(0, 3), // changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListView(children: [
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.5),
|
||||
spreadRadius: 5,
|
||||
blurRadius: 7,
|
||||
offset: const Offset(0, 3), // changes position of shadow
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.all(8),
|
||||
child: const Text("Popover",
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black,
|
||||
fontStyle: null,
|
||||
decoration: null)),
|
||||
),
|
||||
Popover(
|
||||
triggerActions:
|
||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
mutex: popOverMutex,
|
||||
offset: const Offset(10, 0),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return const PopoverMenu();
|
||||
},
|
||||
child: TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text("First"),
|
||||
child: const Text(
|
||||
'Popover',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
Popover(
|
||||
@ -63,33 +52,52 @@ class _PopoverMenuState extends State<PopoverMenu> {
|
||||
},
|
||||
child: TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text("Second"),
|
||||
child: const Text('First'),
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
Popover(
|
||||
triggerActions:
|
||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
mutex: popOverMutex,
|
||||
offset: const Offset(10, 0),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return const PopoverMenu();
|
||||
},
|
||||
child: TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text('Second'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExampleButton extends StatelessWidget {
|
||||
final String label;
|
||||
final Offset? offset;
|
||||
final PopoverDirection? direction;
|
||||
|
||||
const ExampleButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
this.direction,
|
||||
required this.direction,
|
||||
this.offset = Offset.zero,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final Offset? offset;
|
||||
final PopoverDirection direction;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Popover(
|
||||
triggerActions: PopoverTriggerFlags.click,
|
||||
animationDuration: Durations.medium1,
|
||||
offset: offset,
|
||||
direction: direction ?? PopoverDirection.rightWithTopAligned,
|
||||
child: TextButton(child: Text(label), onPressed: () {}),
|
||||
direction: direction,
|
||||
child: TextButton(
|
||||
child: Text(label),
|
||||
onPressed: () {},
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return const PopoverMenu();
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "./example_button.dart";
|
||||
|
||||
import './example_button.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
@ -9,21 +10,11 @@ void main() {
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
// This is the theme of your application.
|
||||
//
|
||||
// Try running your application with "flutter run". You'll see the
|
||||
// application has a blue toolbar. Then, without quitting the app, try
|
||||
// changing the primarySwatch below to Colors.green and then invoke
|
||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
||||
// Notice that the counter didn't reset back to zero; the application
|
||||
// is not restarted.
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'AppFlowy Popover Example'),
|
||||
@ -34,15 +25,6 @@ class MyApp extends StatelessWidget {
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
@ -52,79 +34,82 @@ class MyHomePage extends StatefulWidget {
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: const Row(
|
||||
children: [
|
||||
Column(children: [
|
||||
ExampleButton(
|
||||
label: "Left top",
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
),
|
||||
Expanded(child: SizedBox.shrink()),
|
||||
ExampleButton(
|
||||
label: "Left bottom",
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.topWithLeftAligned,
|
||||
),
|
||||
]),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
body: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 48.0, vertical: 24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: "Top",
|
||||
label: 'Left top',
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
),
|
||||
ExampleButton(
|
||||
label: 'Left Center',
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.rightWithCenterAligned,
|
||||
),
|
||||
ExampleButton(
|
||||
label: 'Left bottom',
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.topWithLeftAligned,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: 'Top',
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: "Central",
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: 'Central',
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
),
|
||||
],
|
||||
),
|
||||
ExampleButton(
|
||||
label: "Bottom",
|
||||
label: 'Bottom',
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.topWithCenterAligned,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: "Right top",
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
),
|
||||
Expanded(child: SizedBox.shrink()),
|
||||
ExampleButton(
|
||||
label: "Right bottom",
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.topWithRightAligned,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ExampleButton(
|
||||
label: 'Right top',
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
),
|
||||
ExampleButton(
|
||||
label: 'Right Center',
|
||||
offset: Offset(0, 10),
|
||||
direction: PopoverDirection.leftWithCenterAligned,
|
||||
),
|
||||
ExampleButton(
|
||||
label: 'Right bottom',
|
||||
offset: Offset(0, -10),
|
||||
direction: PopoverDirection.topWithRightAligned,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 51;
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXAggregateTarget section */
|
||||
@ -182,7 +182,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0920;
|
||||
LastUpgradeCheck = 1300;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
33CC10EC2044A3C60003C045 = {
|
||||
@ -235,6 +235,7 @@
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3399D490228B24CF009A79C7 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
@ -344,7 +345,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@ -423,7 +424,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
@ -470,7 +471,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1300"
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
|
@ -27,7 +27,9 @@ class PopoverCompositedTransformFollower extends CompositedTransformFollower {
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context, PopoverRenderFollowerLayer renderObject) {
|
||||
BuildContext context,
|
||||
PopoverRenderFollowerLayer renderObject,
|
||||
) {
|
||||
final screenSize = MediaQuery.of(context).size;
|
||||
renderObject
|
||||
..screenSize = screenSize
|
||||
@ -40,8 +42,6 @@ class PopoverCompositedTransformFollower extends CompositedTransformFollower {
|
||||
}
|
||||
|
||||
class PopoverRenderFollowerLayer extends RenderFollowerLayer {
|
||||
Size screenSize;
|
||||
|
||||
PopoverRenderFollowerLayer({
|
||||
required super.link,
|
||||
super.showWhenUnlinked = true,
|
||||
@ -52,6 +52,8 @@ class PopoverRenderFollowerLayer extends RenderFollowerLayer {
|
||||
required this.screenSize,
|
||||
});
|
||||
|
||||
Size screenSize;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
super.paint(context, offset);
|
||||
@ -59,13 +61,6 @@ class PopoverRenderFollowerLayer extends RenderFollowerLayer {
|
||||
if (link.leader == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (link.leader!.offset.dx + link.leaderSize!.width + size.width >
|
||||
screenSize.width) {
|
||||
debugPrint("over flow");
|
||||
}
|
||||
debugPrint(
|
||||
"right: ${link.leader!.offset.dx + link.leaderSize!.width + size.width}, screen with: ${screenSize.width}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import './popover.dart';
|
||||
|
||||
class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
PopoverLink link;
|
||||
PopoverDirection direction;
|
||||
final Offset offset;
|
||||
final EdgeInsets windowPadding;
|
||||
|
||||
PopoverLayoutDelegate({
|
||||
required this.link,
|
||||
required this.direction,
|
||||
@ -16,6 +13,11 @@ class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
required this.windowPadding,
|
||||
});
|
||||
|
||||
PopoverLink link;
|
||||
PopoverDirection direction;
|
||||
final Offset offset;
|
||||
final EdgeInsets windowPadding;
|
||||
|
||||
@override
|
||||
bool shouldRelayout(PopoverLayoutDelegate oldDelegate) {
|
||||
if (direction != oldDelegate.direction) {
|
||||
@ -52,141 +54,26 @@ class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
maxHeight:
|
||||
constraints.maxHeight - windowPadding.top - windowPadding.bottom,
|
||||
);
|
||||
// assert(link.leaderSize != null);
|
||||
// // if (link.leaderSize == null) {
|
||||
// // return constraints.loosen();
|
||||
// // }
|
||||
// final anchorRect = Rect.fromLTWH(
|
||||
// link.leaderOffset!.dx,
|
||||
// link.leaderOffset!.dy,
|
||||
// link.leaderSize!.width,
|
||||
// link.leaderSize!.height,
|
||||
// );
|
||||
// BoxConstraints childConstraints;
|
||||
// switch (direction) {
|
||||
// case PopoverDirection.topLeft:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topRight:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomLeft:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomRight:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.center:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithLeftAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.left,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.topWithRightAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.right,
|
||||
// anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithTopAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.rightWithBottomAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth - anchorRect.right,
|
||||
// anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithLeftAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// constraints.maxWidth,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.bottomWithRightAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.right,
|
||||
// constraints.maxHeight - anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithTopAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight - anchorRect.top,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithCenterAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// constraints.maxHeight,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.leftWithBottomAligned:
|
||||
// childConstraints = BoxConstraints.loose(Size(
|
||||
// anchorRect.left,
|
||||
// anchorRect.bottom,
|
||||
// ));
|
||||
// break;
|
||||
// case PopoverDirection.custom:
|
||||
// childConstraints = constraints.loosen();
|
||||
// break;
|
||||
// default:
|
||||
// throw UnimplementedError();
|
||||
// }
|
||||
// return childConstraints;
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
if (link.leaderSize == null) {
|
||||
final leaderOffset = link.leaderOffset;
|
||||
final leaderSize = link.leaderSize;
|
||||
|
||||
if (leaderOffset == null || leaderSize == null) {
|
||||
return Offset.zero;
|
||||
}
|
||||
|
||||
final anchorRect = Rect.fromLTWH(
|
||||
link.leaderOffset!.dx + offset.dx,
|
||||
link.leaderOffset!.dy + offset.dy,
|
||||
link.leaderSize!.width,
|
||||
link.leaderSize!.height,
|
||||
leaderOffset.dx + offset.dx,
|
||||
leaderOffset.dy + offset.dy,
|
||||
leaderSize.width,
|
||||
leaderSize.height,
|
||||
);
|
||||
|
||||
Offset position;
|
||||
|
||||
switch (direction) {
|
||||
case PopoverDirection.topLeft:
|
||||
position = Offset(
|
||||
@ -287,27 +174,35 @@ class PopoverLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
return Offset(
|
||||
math.max(
|
||||
windowPadding.left,
|
||||
math.min(
|
||||
windowPadding.left + size.width - childSize.width, position.dx)),
|
||||
windowPadding.left,
|
||||
math.min(
|
||||
windowPadding.left + size.width - childSize.width,
|
||||
position.dx,
|
||||
),
|
||||
),
|
||||
math.max(
|
||||
windowPadding.top,
|
||||
math.min(
|
||||
windowPadding.top + size.height - childSize.height, position.dy)),
|
||||
windowPadding.top,
|
||||
math.min(
|
||||
windowPadding.top + size.height - childSize.height,
|
||||
position.dy,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverTarget extends SingleChildRenderObjectWidget {
|
||||
final PopoverLink link;
|
||||
const PopoverTarget({
|
||||
super.key,
|
||||
super.child,
|
||||
required this.link,
|
||||
});
|
||||
|
||||
final PopoverLink link;
|
||||
|
||||
@override
|
||||
PopoverTargetRenderBox createRenderObject(BuildContext context) {
|
||||
return PopoverTargetRenderBox(
|
||||
@ -317,14 +212,20 @@ class PopoverTarget extends SingleChildRenderObjectWidget {
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context, PopoverTargetRenderBox renderObject) {
|
||||
BuildContext context,
|
||||
PopoverTargetRenderBox renderObject,
|
||||
) {
|
||||
renderObject.link = link;
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverTargetRenderBox extends RenderProxyBox {
|
||||
PopoverTargetRenderBox({
|
||||
required this.link,
|
||||
RenderBox? child,
|
||||
}) : super(child);
|
||||
|
||||
PopoverLink link;
|
||||
PopoverTargetRenderBox({required this.link, RenderBox? child}) : super(child);
|
||||
|
||||
@override
|
||||
bool get alwaysNeedsCompositing => true;
|
||||
|
@ -3,11 +3,15 @@ import 'dart:collection';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
|
||||
typedef _EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
|
||||
|
||||
class RootOverlayEntry {
|
||||
final EntryMap _entries = EntryMap();
|
||||
RootOverlayEntry();
|
||||
final _EntryMap _entries = _EntryMap();
|
||||
|
||||
bool contains(PopoverState state) => _entries.containsKey(state);
|
||||
|
||||
bool get isEmpty => _entries.isEmpty;
|
||||
bool get isNotEmpty => _entries.isNotEmpty;
|
||||
|
||||
void addEntry(
|
||||
BuildContext context,
|
||||
@ -15,62 +19,54 @@ class RootOverlayEntry {
|
||||
OverlayEntry entry,
|
||||
bool asBarrier,
|
||||
) {
|
||||
_entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
|
||||
_entries[newState] = OverlayEntryContext(
|
||||
entry,
|
||||
newState,
|
||||
asBarrier,
|
||||
);
|
||||
Overlay.of(context).insert(entry);
|
||||
}
|
||||
|
||||
bool contains(PopoverState oldState) {
|
||||
return _entries.containsKey(oldState);
|
||||
}
|
||||
|
||||
void removeEntry(PopoverState oldState) {
|
||||
if (_entries.isEmpty) return;
|
||||
|
||||
final removedEntry = _entries.remove(oldState);
|
||||
void removeEntry(PopoverState state) {
|
||||
final removedEntry = _entries.remove(state);
|
||||
removedEntry?.overlayEntry.remove();
|
||||
}
|
||||
|
||||
bool get isEmpty => _entries.isEmpty;
|
||||
|
||||
bool get isNotEmpty => _entries.isNotEmpty;
|
||||
|
||||
bool hasEntry() {
|
||||
return _entries.isNotEmpty;
|
||||
}
|
||||
|
||||
PopoverState? popEntry() {
|
||||
if (_entries.isEmpty) return null;
|
||||
if (isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final lastEntry = _entries.values.last;
|
||||
_entries.remove(lastEntry.popoverState);
|
||||
lastEntry.overlayEntry.remove();
|
||||
lastEntry.popoverState.widget.onClose?.call();
|
||||
|
||||
if (lastEntry.asBarrier) {
|
||||
return lastEntry.popoverState;
|
||||
} else {
|
||||
return popEntry();
|
||||
}
|
||||
return lastEntry.asBarrier ? lastEntry.popoverState : popEntry();
|
||||
}
|
||||
}
|
||||
|
||||
class OverlayEntryContext {
|
||||
final bool asBarrier;
|
||||
final PopoverState popoverState;
|
||||
final OverlayEntry overlayEntry;
|
||||
|
||||
OverlayEntryContext(
|
||||
this.overlayEntry,
|
||||
this.popoverState,
|
||||
this.asBarrier,
|
||||
);
|
||||
|
||||
final OverlayEntry overlayEntry;
|
||||
final PopoverState popoverState;
|
||||
final bool asBarrier;
|
||||
}
|
||||
|
||||
class PopoverMask extends StatelessWidget {
|
||||
final void Function() onTap;
|
||||
final Decoration? decoration;
|
||||
const PopoverMask({
|
||||
super.key,
|
||||
required this.onTap,
|
||||
this.decoration,
|
||||
});
|
||||
|
||||
const PopoverMask({super.key, required this.onTap, this.decoration});
|
||||
final VoidCallback onTap;
|
||||
final Decoration? decoration;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -5,22 +5,18 @@ import 'popover.dart';
|
||||
/// If multiple popovers are exclusive,
|
||||
/// pass the same mutex to them.
|
||||
class PopoverMutex {
|
||||
final _PopoverStateNotifier _stateNotifier = _PopoverStateNotifier();
|
||||
PopoverMutex();
|
||||
|
||||
final _PopoverStateNotifier _stateNotifier = _PopoverStateNotifier();
|
||||
|
||||
void addPopoverListener(VoidCallback listener) {
|
||||
_stateNotifier.addListener(listener);
|
||||
}
|
||||
|
||||
void removePopoverListener(VoidCallback listener) {
|
||||
_stateNotifier.removeListener(listener);
|
||||
}
|
||||
|
||||
VoidCallback listenOnPopoverChanged(VoidCallback callback) {
|
||||
listenerCallback() {
|
||||
callback();
|
||||
}
|
||||
|
||||
_stateNotifier.addListener(listenerCallback);
|
||||
return listenerCallback;
|
||||
}
|
||||
|
||||
void close() => _stateNotifier.state?.close();
|
||||
|
||||
PopoverState? get state => _stateNotifier.state;
|
||||
|
@ -54,6 +54,33 @@ enum PopoverClickHandler {
|
||||
}
|
||||
|
||||
class Popover extends StatefulWidget {
|
||||
const Popover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.popupBuilder,
|
||||
this.controller,
|
||||
this.offset,
|
||||
this.triggerActions = 0,
|
||||
this.direction = PopoverDirection.rightWithTopAligned,
|
||||
this.mutex,
|
||||
this.windowPadding,
|
||||
this.onOpen,
|
||||
this.onClose,
|
||||
this.canClose,
|
||||
this.asBarrier = false,
|
||||
this.clickHandler = PopoverClickHandler.listener,
|
||||
this.skipTraversal = false,
|
||||
this.animationDuration = const Duration(milliseconds: 200),
|
||||
this.beginOpacity = 0.0,
|
||||
this.endOpacity = 1.0,
|
||||
this.beginScaleFactor = 0.95,
|
||||
this.endScaleFactor = 1.0,
|
||||
this.slideDistance = 20.0,
|
||||
this.maskDecoration = const BoxDecoration(
|
||||
color: Color.fromARGB(0, 244, 67, 54),
|
||||
),
|
||||
});
|
||||
|
||||
final PopoverController? controller;
|
||||
|
||||
/// The offset from the [child] where the popover will be drawn
|
||||
@ -93,113 +120,69 @@ class Popover extends StatefulWidget {
|
||||
|
||||
final bool skipTraversal;
|
||||
|
||||
/// Animation time of the popover.
|
||||
final Duration? animationDuration;
|
||||
|
||||
/// The distance of the popover's slide animation.
|
||||
final double slideDistance;
|
||||
|
||||
/// The scale factor of the popover's scale animation.
|
||||
final double beginScaleFactor;
|
||||
final double endScaleFactor;
|
||||
|
||||
/// The opacity of the popover's fade animation.
|
||||
final double beginOpacity;
|
||||
final double endOpacity;
|
||||
|
||||
/// The content area of the popover.
|
||||
final Widget child;
|
||||
|
||||
const Popover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.popupBuilder,
|
||||
this.controller,
|
||||
this.offset,
|
||||
this.maskDecoration = const BoxDecoration(
|
||||
color: Color.fromARGB(0, 244, 67, 54),
|
||||
),
|
||||
this.triggerActions = 0,
|
||||
this.direction = PopoverDirection.rightWithTopAligned,
|
||||
this.mutex,
|
||||
this.windowPadding,
|
||||
this.onOpen,
|
||||
this.onClose,
|
||||
this.canClose,
|
||||
this.asBarrier = false,
|
||||
this.clickHandler = PopoverClickHandler.listener,
|
||||
this.skipTraversal = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<Popover> createState() => PopoverState();
|
||||
}
|
||||
|
||||
class PopoverState extends State<Popover> {
|
||||
static final RootOverlayEntry _rootEntry = RootOverlayEntry();
|
||||
class PopoverState extends State<Popover> with SingleTickerProviderStateMixin {
|
||||
static final RootOverlayEntry rootEntry = RootOverlayEntry();
|
||||
|
||||
final PopoverLink popoverLink = PopoverLink();
|
||||
late final layoutDelegate = PopoverLayoutDelegate(
|
||||
direction: widget.direction,
|
||||
link: popoverLink,
|
||||
offset: widget.offset ?? Offset.zero,
|
||||
windowPadding: widget.windowPadding ?? EdgeInsets.zero,
|
||||
);
|
||||
|
||||
late AnimationController animationController;
|
||||
late Animation<double> fadeAnimation;
|
||||
late Animation<double> scaleAnimation;
|
||||
late Animation<Offset> slideAnimation;
|
||||
|
||||
// If the widget is disposed, prevent the animation from being called.
|
||||
bool isDisposed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.controller?._state = this;
|
||||
}
|
||||
|
||||
void showOverlay() {
|
||||
close();
|
||||
|
||||
if (widget.mutex != null) {
|
||||
widget.mutex?.state = this;
|
||||
}
|
||||
final shouldAddMask = _rootEntry.isEmpty;
|
||||
final newEntry = OverlayEntry(builder: (context) {
|
||||
final children = <Widget>[];
|
||||
if (shouldAddMask) {
|
||||
children.add(
|
||||
PopoverMask(
|
||||
decoration: widget.maskDecoration,
|
||||
onTap: () async {
|
||||
if (!(await widget.canClose?.call() ?? true)) {
|
||||
return;
|
||||
}
|
||||
_removeRootOverlay();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
children.add(
|
||||
PopoverContainer(
|
||||
direction: widget.direction,
|
||||
popoverLink: popoverLink,
|
||||
offset: widget.offset ?? Offset.zero,
|
||||
windowPadding: widget.windowPadding ?? EdgeInsets.zero,
|
||||
popupBuilder: widget.popupBuilder,
|
||||
onClose: close,
|
||||
onCloseAll: _removeRootOverlay,
|
||||
skipTraversal: widget.skipTraversal,
|
||||
),
|
||||
);
|
||||
|
||||
return CallbackShortcuts(
|
||||
bindings: {
|
||||
const SingleActivator(LogicalKeyboardKey.escape): _removeRootOverlay,
|
||||
},
|
||||
child: FocusScope(child: Stack(children: children)),
|
||||
);
|
||||
});
|
||||
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
|
||||
}
|
||||
|
||||
void close({bool notify = true}) {
|
||||
if (_rootEntry.contains(this)) {
|
||||
_rootEntry.removeEntry(this);
|
||||
if (notify) {
|
||||
widget.onClose?.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removeRootOverlay() {
|
||||
_rootEntry.popEntry();
|
||||
|
||||
if (widget.mutex?.state == this) {
|
||||
widget.mutex?.removeState();
|
||||
}
|
||||
_buildAnimations();
|
||||
}
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
close(notify: false);
|
||||
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isDisposed = true;
|
||||
animationController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverTarget(
|
||||
@ -208,6 +191,66 @@ class PopoverState extends State<Popover> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
// clear the overlay
|
||||
while (rootEntry.isNotEmpty) {
|
||||
rootEntry.popEntry();
|
||||
}
|
||||
|
||||
super.reassemble();
|
||||
}
|
||||
|
||||
void showOverlay() {
|
||||
close();
|
||||
|
||||
if (widget.mutex != null) {
|
||||
widget.mutex?.state = this;
|
||||
}
|
||||
|
||||
final shouldAddMask = rootEntry.isEmpty;
|
||||
rootEntry.addEntry(
|
||||
context,
|
||||
this,
|
||||
OverlayEntry(
|
||||
builder: (context) => _buildOverlayContent(shouldAddMask),
|
||||
),
|
||||
widget.asBarrier,
|
||||
);
|
||||
|
||||
animationController.forward();
|
||||
}
|
||||
|
||||
void close({
|
||||
bool notify = true,
|
||||
bool withAnimation = false,
|
||||
}) {
|
||||
if (rootEntry.contains(this)) {
|
||||
void callback() {
|
||||
rootEntry.removeEntry(this);
|
||||
if (notify) {
|
||||
widget.onClose?.call();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDisposed || !withAnimation) {
|
||||
callback();
|
||||
} else {
|
||||
animationController.reverse().then((_) => callback());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removeRootOverlay() {
|
||||
animationController.reverse().then((_) {
|
||||
rootEntry.popEntry();
|
||||
});
|
||||
|
||||
if (widget.mutex?.state == this) {
|
||||
widget.mutex?.removeState();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildChild(BuildContext context) {
|
||||
if (widget.triggerActions == 0) {
|
||||
return widget.child;
|
||||
@ -247,36 +290,182 @@ class PopoverState extends State<Popover> {
|
||||
}
|
||||
|
||||
void _callHandler(VoidCallback handler) {
|
||||
if (_rootEntry.contains(this)) {
|
||||
if (rootEntry.contains(this)) {
|
||||
close();
|
||||
} else {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildOverlayContent(bool shouldAddMask) {
|
||||
return CallbackShortcuts(
|
||||
bindings: {
|
||||
const SingleActivator(LogicalKeyboardKey.escape): _removeRootOverlay,
|
||||
},
|
||||
child: FocusScope(
|
||||
child: Stack(
|
||||
children: [
|
||||
if (shouldAddMask) _buildMask(),
|
||||
_buildPopoverContainer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMask() {
|
||||
return PopoverMask(
|
||||
decoration: widget.maskDecoration,
|
||||
onTap: () async {
|
||||
if (await widget.canClose?.call() ?? true) {
|
||||
_removeRootOverlay();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPopoverContainer() {
|
||||
return AnimatedBuilder(
|
||||
animation: animationController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: fadeAnimation.value,
|
||||
child: Transform.scale(
|
||||
scale: scaleAnimation.value,
|
||||
child: Transform.translate(
|
||||
offset: slideAnimation.value,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: PopoverContainer(
|
||||
delegate: layoutDelegate,
|
||||
popupBuilder: widget.popupBuilder,
|
||||
skipTraversal: widget.skipTraversal,
|
||||
onClose: close,
|
||||
onCloseAll: _removeRootOverlay,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _buildAnimations() {
|
||||
animationController = AnimationController(
|
||||
duration: widget.animationDuration,
|
||||
vsync: this,
|
||||
);
|
||||
fadeAnimation = _buildFadeAnimation();
|
||||
scaleAnimation = _buildScaleAnimation();
|
||||
slideAnimation = _buildSlideAnimation();
|
||||
}
|
||||
|
||||
Animation<double> _buildFadeAnimation() {
|
||||
return Tween<double>(
|
||||
begin: widget.beginOpacity,
|
||||
end: widget.endOpacity,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Animation<double> _buildScaleAnimation() {
|
||||
return Tween<double>(
|
||||
begin: widget.beginScaleFactor,
|
||||
end: widget.endScaleFactor,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Animation<Offset> _buildSlideAnimation() {
|
||||
final values = _getSlideAnimationValues();
|
||||
return Tween<Offset>(
|
||||
begin: values.$1,
|
||||
end: values.$2,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
(Offset, Offset) _getSlideAnimationValues() {
|
||||
final slideDistance = widget.slideDistance;
|
||||
|
||||
switch (widget.direction) {
|
||||
case PopoverDirection.bottomWithLeftAligned:
|
||||
return (
|
||||
Offset(-slideDistance, -slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.bottomWithCenterAligned:
|
||||
return (
|
||||
Offset(0, -slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.bottomWithRightAligned:
|
||||
return (
|
||||
Offset(slideDistance, -slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.topWithLeftAligned:
|
||||
return (
|
||||
Offset(-slideDistance, slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.topWithCenterAligned:
|
||||
return (
|
||||
Offset(0, slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.topWithRightAligned:
|
||||
return (
|
||||
Offset(slideDistance, slideDistance),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.leftWithTopAligned:
|
||||
case PopoverDirection.leftWithCenterAligned:
|
||||
case PopoverDirection.leftWithBottomAligned:
|
||||
return (
|
||||
Offset(slideDistance, 0),
|
||||
Offset.zero,
|
||||
);
|
||||
case PopoverDirection.rightWithTopAligned:
|
||||
case PopoverDirection.rightWithCenterAligned:
|
||||
case PopoverDirection.rightWithBottomAligned:
|
||||
return (
|
||||
Offset(-slideDistance, 0),
|
||||
Offset.zero,
|
||||
);
|
||||
default:
|
||||
return (Offset.zero, Offset.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PopoverContainer extends StatefulWidget {
|
||||
final Widget? Function(BuildContext context) popupBuilder;
|
||||
final PopoverDirection direction;
|
||||
final PopoverLink popoverLink;
|
||||
final Offset offset;
|
||||
final EdgeInsets windowPadding;
|
||||
final void Function() onClose;
|
||||
final void Function() onCloseAll;
|
||||
final bool skipTraversal;
|
||||
|
||||
const PopoverContainer({
|
||||
super.key,
|
||||
required this.popupBuilder,
|
||||
required this.direction,
|
||||
required this.popoverLink,
|
||||
required this.offset,
|
||||
required this.windowPadding,
|
||||
required this.delegate,
|
||||
required this.onClose,
|
||||
required this.onCloseAll,
|
||||
required this.skipTraversal,
|
||||
});
|
||||
|
||||
final Widget? Function(BuildContext context) popupBuilder;
|
||||
final void Function() onClose;
|
||||
final void Function() onCloseAll;
|
||||
final bool skipTraversal;
|
||||
final PopoverLayoutDelegate delegate;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => PopoverContainerState();
|
||||
|
||||
@ -302,12 +491,7 @@ class PopoverContainerState extends State<PopoverContainer> {
|
||||
autofocus: true,
|
||||
skipTraversal: widget.skipTraversal,
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: PopoverLayoutDelegate(
|
||||
direction: widget.direction,
|
||||
link: widget.popoverLink,
|
||||
offset: widget.offset,
|
||||
windowPadding: widget.windowPadding,
|
||||
),
|
||||
delegate: widget.delegate,
|
||||
child: widget.popupBuilder(context),
|
||||
),
|
||||
);
|
||||
|
@ -4,8 +4,8 @@ version: 0.0.1
|
||||
homepage: https://appflowy.io
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.10.1"
|
||||
flutter: ">=3.22.0"
|
||||
sdk: ">=3.3.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@ -14,41 +14,4 @@ dependencies:
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.1
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
flutter_lints: ^4.0.0
|
||||
|
@ -1,37 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/colorscheme/default_colorscheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppFlowyPopover extends StatelessWidget {
|
||||
final Widget child;
|
||||
final PopoverController? controller;
|
||||
final Widget Function(BuildContext context) popupBuilder;
|
||||
final PopoverDirection direction;
|
||||
final int triggerActions;
|
||||
final BoxConstraints constraints;
|
||||
final VoidCallback? onOpen;
|
||||
final VoidCallback? onClose;
|
||||
final Future<bool> Function()? canClose;
|
||||
final PopoverMutex? mutex;
|
||||
final Offset? offset;
|
||||
final bool asBarrier;
|
||||
final EdgeInsets margin;
|
||||
final EdgeInsets windowPadding;
|
||||
final Color? decorationColor;
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// The widget that will be used to trigger the popover.
|
||||
///
|
||||
/// Why do we need this?
|
||||
/// Because if the parent widget of the popover is GestureDetector,
|
||||
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
||||
final PopoverClickHandler clickHandler;
|
||||
|
||||
/// If true the popover will not participate in focus traversal.
|
||||
///
|
||||
final bool skipTraversal;
|
||||
|
||||
const AppFlowyPopover({
|
||||
super.key,
|
||||
required this.child,
|
||||
@ -52,12 +23,46 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
this.skipTraversal = false,
|
||||
this.decorationColor,
|
||||
this.borderRadius,
|
||||
this.animationDuration = const Duration(),
|
||||
this.slideDistance = 20.0,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final PopoverController? controller;
|
||||
final Widget Function(BuildContext context) popupBuilder;
|
||||
final PopoverDirection direction;
|
||||
final int triggerActions;
|
||||
final BoxConstraints constraints;
|
||||
final VoidCallback? onOpen;
|
||||
final VoidCallback? onClose;
|
||||
final Future<bool> Function()? canClose;
|
||||
final PopoverMutex? mutex;
|
||||
final Offset? offset;
|
||||
final bool asBarrier;
|
||||
final EdgeInsets margin;
|
||||
final EdgeInsets windowPadding;
|
||||
final Color? decorationColor;
|
||||
final BorderRadius? borderRadius;
|
||||
final Duration animationDuration;
|
||||
final double slideDistance;
|
||||
|
||||
/// The widget that will be used to trigger the popover.
|
||||
///
|
||||
/// Why do we need this?
|
||||
/// Because if the parent widget of the popover is GestureDetector,
|
||||
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
||||
final PopoverClickHandler clickHandler;
|
||||
|
||||
/// If true the popover will not participate in focus traversal.
|
||||
///
|
||||
final bool skipTraversal;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Popover(
|
||||
controller: controller,
|
||||
animationDuration: animationDuration,
|
||||
slideDistance: slideDistance,
|
||||
onOpen: onOpen,
|
||||
onClose: onClose,
|
||||
canClose: canClose,
|
||||
|
@ -1535,10 +1535,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.1.5"
|
||||
plugin_platform_interface:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1933,10 +1933,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
string_validator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -2238,10 +2238,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.1"
|
||||
version: "14.2.5"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
Loading…
x
Reference in New Issue
Block a user