[flutter]: config home screen question bubble

This commit is contained in:
appflowy 2021-11-06 19:45:23 +08:00
parent 7246f597af
commit f1251e7152
16 changed files with 265 additions and 70 deletions

View file

@ -2,9 +2,14 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:styled_widget/styled_widget.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class QuestionBubble extends StatelessWidget { class QuestionBubble extends StatelessWidget {
const QuestionBubble({Key? key}) : super(key: key); const QuestionBubble({Key? key}) : super(key: key);
@ -17,22 +22,45 @@ class QuestionBubble extends StatelessWidget {
height: 30, height: 30,
child: FlowyTextButton( child: FlowyTextButton(
'?', '?',
tooltip: QuestionBubbleAction.values.map((action) => action.name).toList().join(','),
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fillColor: theme.selector, fillColor: theme.selector,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
radius: BorderRadius.circular(10), radius: BorderRadius.circular(10),
onPressed: () { onPressed: () {
final actionList = QuestionBubbleActions(onSelected: (action) {}); final actionList = QuestionBubbleActions(onSelected: (result) {
result.fold(() {}, (action) {
switch (action) {
case QuestionBubbleAction.whatsNews:
// TODO: annie replace the URL with real ones
_launchURL("https://www.google.com");
break;
case QuestionBubbleAction.help:
// TODO: annie replace the URL with real ones
_launchURL("https://www.google.com");
break;
}
});
});
actionList.show( actionList.show(
context, context,
context, context,
anchorDirection: AnchorDirection.topWithCenterAligned, anchorDirection: AnchorDirection.topWithRightAligned,
anchorOffset: const Offset(0, -10),
); );
}, },
), ),
); );
} }
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
} }
class QuestionBubbleActions with ActionList<QuestionBubbleActionWrapper> implements FlowyOverlayDelegate { class QuestionBubbleActions with ActionList<QuestionBubbleActionWrapper> implements FlowyOverlayDelegate {
@ -43,6 +71,12 @@ class QuestionBubbleActions with ActionList<QuestionBubbleActionWrapper> impleme
required this.onSelected, required this.onSelected,
}); });
@override
double get maxWidth => 170;
@override
double get itemHeight => 22;
@override @override
List<QuestionBubbleActionWrapper> get items => _items; List<QuestionBubbleActionWrapper> get items => _items;
@ -63,10 +97,57 @@ class QuestionBubbleActions with ActionList<QuestionBubbleActionWrapper> impleme
void didRemove() { void didRemove() {
onSelected(dartz.none()); onSelected(dartz.none());
} }
@override
ListOverlayFooter? get footer => ListOverlayFooter(
widget: const FlowyVersionDescription(),
height: 30,
padding: const EdgeInsets.only(top: 6),
);
}
class FlowyVersionDescription extends StatelessWidget {
const FlowyVersionDescription({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FutureBuilder(
future: PackageInfo.fromPlatform(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return FlowyText("Error: ${snapshot.error}", fontSize: 12, color: theme.shader4);
}
PackageInfo packageInfo = snapshot.data;
String appName = packageInfo.appName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(height: 1, color: theme.shader6, thickness: 1.0),
const VSpace(6),
FlowyText("$appName $version.$buildNumber", fontSize: 12, color: theme.shader4),
],
).padding(
horizontal: ActionListSizes.itemHPadding + ActionListSizes.padding,
);
} else {
return const CircularProgressIndicator();
}
},
);
}
} }
enum QuestionBubbleAction { enum QuestionBubbleAction {
whatsNews, whatsNews,
help,
} }
class QuestionBubbleActionWrapper extends ActionItemData { class QuestionBubbleActionWrapper extends ActionItemData {
@ -74,7 +155,7 @@ class QuestionBubbleActionWrapper extends ActionItemData {
QuestionBubbleActionWrapper(this.inner); QuestionBubbleActionWrapper(this.inner);
@override @override
Widget? get icon => null; Widget? get icon => inner.emoji;
@override @override
String get name => inner.name; String get name => inner.name;
@ -85,6 +166,17 @@ extension QuestionBubbleExtension on QuestionBubbleAction {
switch (this) { switch (this) {
case QuestionBubbleAction.whatsNews: case QuestionBubbleAction.whatsNews:
return "What's new"; return "What's new";
case QuestionBubbleAction.help:
return "Help & Support";
}
}
Widget get emoji {
switch (this) {
case QuestionBubbleAction.whatsNews:
return const Text('😘', style: TextStyle(fontSize: 16));
case QuestionBubbleAction.help:
return const Text('💁🏻', style: TextStyle(fontSize: 16));
} }
} }
} }

View file

@ -55,8 +55,8 @@ class ActionList {
itemBuilder: (context, index) => items[index], itemBuilder: (context, index) => items[index],
anchorContext: anchorContext, anchorContext: anchorContext,
anchorDirection: AnchorDirection.bottomRight, anchorDirection: AnchorDirection.bottomRight,
maxWidth: 120, width: 120,
maxHeight: 80, height: 80,
); );
} }
} }

View file

@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:dartz/dartz.dart' as dartz; import 'package:dartz/dartz.dart' as dartz;
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
class AppDisclosureActions with ActionList<AppDisclosureActionWrapper> implements FlowyOverlayDelegate { class AppDisclosureActions with ActionList<AppDisclosureActionWrapper> implements FlowyOverlayDelegate {
final Function(dartz.Option<AppDisclosureAction>) onSelected; final Function(dartz.Option<AppDisclosureAction>) onSelected;
@ -32,6 +33,9 @@ class AppDisclosureActions with ActionList<AppDisclosureActionWrapper> implement
void didRemove() { void didRemove() {
onSelected(dartz.none()); onSelected(dartz.none());
} }
@override
ListOverlayFooter? get footer => null;
} }
class AppDisclosureActionWrapper extends ActionItemData { class AppDisclosureActionWrapper extends ActionItemData {
@ -39,7 +43,7 @@ class AppDisclosureActionWrapper extends ActionItemData {
AppDisclosureActionWrapper(this.inner); AppDisclosureActionWrapper(this.inner);
@override @override
Widget get icon => inner.icon; Widget? get icon => inner.icon;
@override @override
String get name => inner.name; String get name => inner.name;

View file

@ -5,6 +5,7 @@ import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:tuple/tuple.dart';
// [[Widget: LifeCycle]] // [[Widget: LifeCycle]]
// https://flutterbyexample.com/lesson/stateful-widget-lifecycle // https://flutterbyexample.com/lesson/stateful-widget-lifecycle
@ -37,9 +38,6 @@ class ViewDisclosureButton extends StatelessWidget
@override @override
List<ViewDisclosureActionWrapper> get items => _items; List<ViewDisclosureActionWrapper> get items => _items;
@override
double get maxWidth => 162;
@override @override
void Function(dartz.Option<ViewDisclosureActionWrapper> p1) get selectCallback => (result) { void Function(dartz.Option<ViewDisclosureActionWrapper> p1) get selectCallback => (result) {
result.fold( result.fold(
@ -55,6 +53,9 @@ class ViewDisclosureButton extends StatelessWidget
void didRemove() { void didRemove() {
onSelected(dartz.none()); onSelected(dartz.none());
} }
@override
ListOverlayFooter? get footer => null;
} }
class ViewDisclosureActionWrapper extends ActionItemData { class ViewDisclosureActionWrapper extends ActionItemData {
@ -62,7 +63,7 @@ class ViewDisclosureActionWrapper extends ActionItemData {
ViewDisclosureActionWrapper(this.inner); ViewDisclosureActionWrapper(this.inner);
@override @override
Widget get icon => inner.icon; Widget? get icon => inner.icon;
@override @override
String get name => inner.name; String get name => inner.name;

View file

@ -15,23 +15,33 @@ abstract class ActionList<T extends ActionItemData> {
double get maxWidth => 162; double get maxWidth => 162;
double get itemHeight => ActionListSizes.itemHeight;
ListOverlayFooter? get footer;
void Function(dartz.Option<T>) get selectCallback; void Function(dartz.Option<T>) get selectCallback;
FlowyOverlayDelegate? get delegate; FlowyOverlayDelegate? get delegate;
void show(BuildContext buildContext, BuildContext anchorContext, void show(
{AnchorDirection anchorDirection = AnchorDirection.bottomRight}) { BuildContext buildContext,
BuildContext anchorContext, {
AnchorDirection anchorDirection = AnchorDirection.bottomRight,
Offset? anchorOffset,
}) {
final widgets = items final widgets = items
.map((action) => ActionItem<T>( .map(
(action) => ActionItem<T>(
action: action, action: action,
itemHeight: itemHeight,
onSelected: (action) { onSelected: (action) {
FlowyOverlay.of(buildContext).remove(identifier); FlowyOverlay.of(buildContext).remove(identifier);
selectCallback(dartz.some(action)); selectCallback(dartz.some(action));
})) },
),
)
.toList(); .toList();
double totalHeight = widgets.length * (ActionListSizes.itemHeight + ActionListSizes.padding * 2);
ListOverlay.showWithAnchor( ListOverlay.showWithAnchor(
buildContext, buildContext,
identifier: identifier, identifier: identifier,
@ -39,9 +49,11 @@ abstract class ActionList<T extends ActionItemData> {
itemBuilder: (context, index) => widgets[index], itemBuilder: (context, index) => widgets[index],
anchorContext: anchorContext, anchorContext: anchorContext,
anchorDirection: anchorDirection, anchorDirection: anchorDirection,
maxWidth: maxWidth, width: maxWidth,
maxHeight: totalHeight, height: widgets.length * (itemHeight + ActionListSizes.padding * 2),
delegate: delegate, delegate: delegate,
anchorOffset: anchorOffset,
footer: footer,
); );
} }
} }
@ -60,10 +72,12 @@ class ActionListSizes {
class ActionItem<T extends ActionItemData> extends StatelessWidget { class ActionItem<T extends ActionItemData> extends StatelessWidget {
final T action; final T action;
final Function(T) onSelected; final Function(T) onSelected;
final double itemHeight;
const ActionItem({ const ActionItem({
Key? key, Key? key,
required this.action, required this.action,
required this.onSelected, required this.onSelected,
required this.itemHeight,
}) : super(key: key); }) : super(key: key);
@override @override
@ -77,7 +91,7 @@ class ActionItem<T extends ActionItemData> extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => onSelected(action), onTap: () => onSelected(action),
child: SizedBox( child: SizedBox(
height: ActionListSizes.itemHeight, height: itemHeight,
child: Row( child: Row(
children: [ children: [
if (action.icon != null) action.icon!, if (action.icon != null) action.icon!,

View file

@ -8,6 +8,7 @@ import Foundation
import flowy_editor import flowy_editor
import flowy_infra_ui import flowy_infra_ui
import flowy_sdk import flowy_sdk
import package_info_plus_macos
import path_provider_macos import path_provider_macos
import url_launcher_macos import url_launcher_macos
import window_size import window_size
@ -16,6 +17,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlowyEditorPlugin.register(with: registry.registrar(forPlugin: "FlowyEditorPlugin")) FlowyEditorPlugin.register(with: registry.registrar(forPlugin: "FlowyEditorPlugin"))
FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin")) FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin"))
FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin")) FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin")) WindowSizePlugin.register(with: registry.registrar(forPlugin: "WindowSizePlugin"))

View file

@ -6,6 +6,8 @@ PODS:
- flowy_sdk (0.0.1): - flowy_sdk (0.0.1):
- FlutterMacOS - FlutterMacOS
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1): - path_provider_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
@ -18,6 +20,7 @@ DEPENDENCIES:
- flowy_infra_ui (from `Flutter/ephemeral/.symlinks/plugins/flowy_infra_ui/macos`) - flowy_infra_ui (from `Flutter/ephemeral/.symlinks/plugins/flowy_infra_ui/macos`)
- flowy_sdk (from `Flutter/ephemeral/.symlinks/plugins/flowy_sdk/macos`) - flowy_sdk (from `Flutter/ephemeral/.symlinks/plugins/flowy_sdk/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`)
@ -31,6 +34,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/flowy_sdk/macos :path: Flutter/ephemeral/.symlinks/plugins/flowy_sdk/macos
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
package_info_plus_macos:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos: path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
url_launcher_macos: url_launcher_macos:
@ -43,6 +48,7 @@ SPEC CHECKSUMS:
flowy_infra_ui: 9d5021b1610fe0476eb1191bf7cd41c4a4138d8f flowy_infra_ui: 9d5021b1610fe0476eb1191bf7cd41c4a4138d8f
flowy_sdk: c302ac0a22dea596db0df8073b9637b2bf2ff6fd flowy_sdk: c302ac0a22dea596db0df8073b9637b2bf2ff6fd
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f
url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4 url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4
window_size: 339dafa0b27a95a62a843042038fa6c3c48de195 window_size: 339dafa0b27a95a62a843042038fa6c3c48de195

View file

@ -57,7 +57,7 @@
1CD81A6C7244B2318E0BA2E8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1CD81A6C7244B2318E0BA2E8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* app_flowy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app_flowy.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* AppFlowy.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppFlowy.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -112,7 +112,7 @@
33CC10EE2044A3C60003C045 /* Products */ = { 33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
33CC10ED2044A3C60003C045 /* app_flowy.app */, 33CC10ED2044A3C60003C045 /* AppFlowy.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@ -192,7 +192,7 @@
); );
name = Runner; name = Runner;
productName = Runner; productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* app_flowy.app */; productReference = 33CC10ED2044A3C60003C045 /* AppFlowy.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
@ -425,6 +425,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
STRIP_STYLE = "non-global"; STRIP_STYLE = "non-global";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -552,6 +553,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
STRIP_STYLE = "non-global"; STRIP_STYLE = "non-global";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -573,6 +575,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
STRIP_STYLE = "non-global"; STRIP_STYLE = "non-global";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View file

@ -15,7 +15,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app_flowy.app" BuildableName = "AppFlowy.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
@ -31,13 +31,13 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app_flowy.app" BuildableName = "AppFlowy.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions> <Testables>
</AdditionalOptions> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Debug" buildConfiguration = "Debug"
@ -54,13 +54,11 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app_flowy.app" BuildableName = "AppFlowy.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
buildConfiguration = "Profile" buildConfiguration = "Profile"
@ -73,7 +71,7 @@
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045" BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app_flowy.app" BuildableName = "AppFlowy.app"
BlueprintName = "Runner" BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj"> ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference> </BuildableReference>

View file

@ -188,8 +188,8 @@ class OverlayScreen extends StatelessWidget {
anchorContext: buttonContext, anchorContext: buttonContext,
anchorDirection: providerContext.read<OverlayDemoConfiguration>().anchorDirection, anchorDirection: providerContext.read<OverlayDemoConfiguration>().anchorDirection,
overlapBehaviour: providerContext.read<OverlayDemoConfiguration>().overlapBehaviour, overlapBehaviour: providerContext.read<OverlayDemoConfiguration>().overlapBehaviour,
maxWidth: 200.0, width: 200.0,
maxHeight: 200.0, height: 200.0,
); );
}, },
child: const Text('Show List Overlay'), child: const Text('Show List Overlay'),

View file

@ -163,7 +163,7 @@ class FlowyOverlayState extends State<FlowyOverlay> {
FlowyOverlayDelegate? delegate, FlowyOverlayDelegate? delegate,
OverlapBehaviour? overlapBehaviour, OverlapBehaviour? overlapBehaviour,
FlowyOverlayStyle? style, FlowyOverlayStyle? style,
Offset? anchorPosition, Offset? anchorOffset,
}) { }) {
this.style = style ?? FlowyOverlayStyle(); this.style = style ?? FlowyOverlayStyle();
@ -175,7 +175,7 @@ class FlowyOverlayState extends State<FlowyOverlay> {
anchorContext: anchorContext, anchorContext: anchorContext,
anchorDirection: anchorDirection, anchorDirection: anchorDirection,
overlapBehaviour: overlapBehaviour, overlapBehaviour: overlapBehaviour,
anchorPosition: anchorPosition, anchorOffset: anchorOffset,
); );
} }
@ -209,10 +209,12 @@ class FlowyOverlayState extends State<FlowyOverlay> {
Size? anchorSize, Size? anchorSize,
AnchorDirection? anchorDirection, AnchorDirection? anchorDirection,
BuildContext? anchorContext, BuildContext? anchorContext,
Offset? anchorOffset,
OverlapBehaviour? overlapBehaviour, OverlapBehaviour? overlapBehaviour,
FlowyOverlayDelegate? delegate, FlowyOverlayDelegate? delegate,
}) { }) {
Widget overlay = widget; Widget overlay = widget;
final offset = anchorOffset ?? Offset.zero;
if (shouldAnchor) { if (shouldAnchor) {
assert( assert(
@ -232,8 +234,8 @@ class FlowyOverlayState extends State<FlowyOverlay> {
targetAnchorSize = renderBox.size; targetAnchorSize = renderBox.size;
} }
final anchorRect = Rect.fromLTWH( final anchorRect = Rect.fromLTWH(
targetAnchorPosition.dx, targetAnchorPosition.dx + offset.dx,
targetAnchorPosition.dy, targetAnchorPosition.dy + offset.dy,
targetAnchorSize.width, targetAnchorSize.width,
targetAnchorSize.height, targetAnchorSize.height,
); );

View file

@ -2,38 +2,68 @@ import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/decoration.dart'; import 'package:flowy_infra_ui/style_widget/decoration.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ListOverlayFooter {
Widget widget;
double height;
EdgeInsets padding;
ListOverlayFooter({
required this.widget,
required this.height,
this.padding = EdgeInsets.zero,
});
}
class ListOverlay extends StatelessWidget { class ListOverlay extends StatelessWidget {
const ListOverlay({ const ListOverlay({
Key? key, Key? key,
required this.itemBuilder, required this.itemBuilder,
this.itemCount, this.itemCount,
this.controller, this.controller,
this.maxWidth = double.infinity, this.width = double.infinity,
this.maxHeight = double.infinity, this.height = double.infinity,
this.footer,
}) : super(key: key); }) : super(key: key);
final IndexedWidgetBuilder itemBuilder; final IndexedWidgetBuilder itemBuilder;
final int? itemCount; final int? itemCount;
final ScrollController? controller; final ScrollController? controller;
final double maxWidth; final double width;
final double maxHeight; final double height;
final ListOverlayFooter? footer;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const padding = EdgeInsets.symmetric(horizontal: 6, vertical: 6); const padding = EdgeInsets.symmetric(horizontal: 6, vertical: 6);
double totalHeight = height + padding.vertical;
if (footer != null) {
totalHeight = totalHeight + footer!.height + footer!.padding.vertical;
}
return Material( return Material(
type: MaterialType.transparency, type: MaterialType.transparency,
child: Container( child: Container(
constraints: BoxConstraints.tight(Size(maxWidth, maxHeight + padding.vertical)),
decoration: FlowyDecoration.decoration(), decoration: FlowyDecoration.decoration(),
constraints: BoxConstraints.tight(Size(width, totalHeight)),
child: Padding( child: Padding(
padding: padding, padding: padding,
child: ListView.builder( child: Column(
children: [
ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
itemCount: itemCount, itemCount: itemCount,
controller: controller, controller: controller,
), ),
if (footer != null)
SizedBox(
height: footer!.height,
child: Padding(
padding: footer!.padding,
child: SizedBox.expand(child: footer!.widget),
),
),
],
),
), ),
), ),
); );
@ -45,27 +75,31 @@ class ListOverlay extends StatelessWidget {
required IndexedWidgetBuilder itemBuilder, required IndexedWidgetBuilder itemBuilder,
int? itemCount, int? itemCount,
ScrollController? controller, ScrollController? controller,
double maxWidth = double.infinity, double width = double.infinity,
double maxHeight = double.infinity, double height = double.infinity,
required BuildContext anchorContext, required BuildContext anchorContext,
AnchorDirection? anchorDirection, AnchorDirection? anchorDirection,
FlowyOverlayDelegate? delegate, FlowyOverlayDelegate? delegate,
OverlapBehaviour? overlapBehaviour, OverlapBehaviour? overlapBehaviour,
FlowyOverlayStyle? style, FlowyOverlayStyle? style,
Offset? anchorOffset,
ListOverlayFooter? footer,
}) { }) {
FlowyOverlay.of(context).insertWithAnchor( FlowyOverlay.of(context).insertWithAnchor(
widget: ListOverlay( widget: ListOverlay(
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
itemCount: itemCount, itemCount: itemCount,
controller: controller, controller: controller,
maxWidth: maxWidth, width: width,
maxHeight: maxHeight, height: height,
footer: footer,
), ),
identifier: identifier, identifier: identifier,
anchorContext: anchorContext, anchorContext: anchorContext,
anchorDirection: anchorDirection, anchorDirection: anchorDirection,
delegate: delegate, delegate: delegate,
overlapBehaviour: overlapBehaviour, overlapBehaviour: overlapBehaviour,
anchorOffset: anchorOffset,
style: style, style: style,
); );
} }
@ -91,8 +125,8 @@ class ListOverlay extends StatelessWidget {
itemBuilder: itemBuilder, itemBuilder: itemBuilder,
itemCount: itemCount, itemCount: itemCount,
controller: controller, controller: controller,
maxWidth: maxWidth, width: maxWidth,
maxHeight: maxHeight, height: maxHeight,
), ),
identifier: identifier, identifier: identifier,
anchorPosition: anchorPosition, anchorPosition: anchorPosition,

View file

@ -22,14 +22,14 @@ class OptionOverlay<T> extends StatelessWidget {
static void showWithAnchor<T>( static void showWithAnchor<T>(
BuildContext context, { BuildContext context, {
required String identifier,
required List<T> items, required List<T> items,
required String identifier,
required BuildContext anchorContext,
IndexedValueCallback<T>? onHover, IndexedValueCallback<T>? onHover,
IndexedValueCallback<T>? onTap, IndexedValueCallback<T>? onTap,
required BuildContext anchorContext,
AnchorDirection? anchorDirection, AnchorDirection? anchorDirection,
FlowyOverlayDelegate? delegate,
OverlapBehaviour? overlapBehaviour, OverlapBehaviour? overlapBehaviour,
FlowyOverlayDelegate? delegate,
}) { }) {
FlowyOverlay.of(context).insertWithAnchor( FlowyOverlay.of(context).insertWithAnchor(
widget: OptionOverlay( widget: OptionOverlay(

View file

@ -65,6 +65,7 @@ class FlowyTextButton extends StatelessWidget {
final Color? fillColor; final Color? fillColor;
final BorderRadius? radius; final BorderRadius? radius;
final MainAxisAlignment mainAxisAlignment; final MainAxisAlignment mainAxisAlignment;
final String? tooltip;
// final HoverDisplayConfig? hoverDisplay; // final HoverDisplayConfig? hoverDisplay;
const FlowyTextButton( const FlowyTextButton(
@ -80,6 +81,7 @@ class FlowyTextButton extends StatelessWidget {
this.heading, this.heading,
this.radius, this.radius,
this.mainAxisAlignment = MainAxisAlignment.start, this.mainAxisAlignment = MainAxisAlignment.start,
this.tooltip,
}) : super(key: key); }) : super(key: key);
@override @override
@ -108,7 +110,7 @@ class FlowyTextButton extends StatelessWidget {
), ),
); );
return RawMaterialButton( child = RawMaterialButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
hoverElevation: 0, hoverElevation: 0,
highlightElevation: 0, highlightElevation: 0,
@ -123,20 +125,14 @@ class FlowyTextButton extends StatelessWidget {
child: child, child: child,
); );
// if (hoverColor != null) { if (tooltip != null) {
// return InkWell( child = Tooltip(
// onTap: onPressed, message: tooltip!,
// child: FlowyHover( child: child,
// config: HoverDisplayConfig(borderRadius: radius ?? BorderRadius.circular(6), hoverColor: hoverColor!), );
// builder: (context, onHover) => child, }
// ),
// ); return child;
// } else {
// return InkWell(
// onTap: onPressed,
// child: child,
// );
// }
} }
} }
// return TextButton( // return TextButton(

View file

@ -597,6 +597,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
package_info_plus_linux:
dependency: transitive
description:
name: package_info_plus_linux
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
package_info_plus_macos:
dependency: transitive
description:
name: package_info_plus_macos
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
package_info_plus_web:
dependency: transitive
description:
name: package_info_plus_web
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
package_info_plus_windows:
dependency: transitive
description:
name: package_info_plus_windows
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
path: path:
dependency: transitive dependency: transitive
description: description:

View file

@ -63,6 +63,7 @@ dependencies:
expandable: ^5.0.1 expandable: ^5.0.1
flutter_svg: ^0.22.0 flutter_svg: ^0.22.0
flutter_colorpicker: ^0.6.0 flutter_colorpicker: ^0.6.0
package_info_plus: ^1.3.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.