Browse Source

[flutter]: config home screen question bubble

appflowy 3 years ago
parent
commit
f1251e7152

+ 95 - 3
app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart

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

+ 2 - 2
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/add_button.dart

@@ -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,
-      maxHeight: 80,
+      width: 120,
+      height: 80,
     );
     );
   }
   }
 }
 }

+ 5 - 1
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/header/right_click_action.dart

@@ -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;

+ 5 - 4
app_flowy/lib/workspace/presentation/widgets/menu/widget/app/section/disclosure_action.dart

@@ -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;

+ 23 - 9
app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart

@@ -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,
-      {AnchorDirection anchorDirection = AnchorDirection.bottomRight}) {
+  void show(
+    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,
-      maxHeight: totalHeight,
+      width: maxWidth,
+      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!,

+ 2 - 0
app_flowy/macos/Flutter/GeneratedPluginRegistrant.swift

@@ -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"))

+ 6 - 0
app_flowy/macos/Podfile.lock

@@ -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

+ 6 - 3
app_flowy/macos/Runner.xcodeproj/project.pbxproj

@@ -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;

+ 6 - 8
app_flowy/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -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>
-      </AdditionalOptions>
+      <Testables>
+      </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>

+ 2 - 2
app_flowy/packages/flowy_infra_ui/example/lib/overlay/overlay_screen.dart

@@ -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,
-                          maxHeight: 200.0,
+                          width: 200.0,
+                          height: 200.0,
                         );
                         );
                       },
                       },
                       child: const Text('Show List Overlay'),
                       child: const Text('Show List Overlay'),

+ 6 - 4
app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart

@@ -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.dy,
+        targetAnchorPosition.dx + offset.dx,
+        targetAnchorPosition.dy + offset.dy,
         targetAnchorSize.width,
         targetAnchorSize.width,
         targetAnchorSize.height,
         targetAnchorSize.height,
       );
       );

+ 50 - 16
app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart

@@ -2,37 +2,67 @@ 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.maxHeight = double.infinity,
+    this.width = 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 maxHeight;
+  final double width;
+  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(
-            shrinkWrap: true,
-            itemBuilder: itemBuilder,
-            itemCount: itemCount,
-            controller: controller,
+          child: Column(
+            children: [
+              ListView.builder(
+                shrinkWrap: true,
+                itemBuilder: itemBuilder,
+                itemCount: itemCount,
+                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 maxHeight = double.infinity,
+    double width = 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,
-        maxHeight: maxHeight,
+        width: width,
+        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,
-        maxHeight: maxHeight,
+        width: maxWidth,
+        height: maxHeight,
       ),
       ),
       identifier: identifier,
       identifier: identifier,
       anchorPosition: anchorPosition,
       anchorPosition: anchorPosition,

+ 3 - 3
app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/option_overlay.dart

@@ -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(

+ 11 - 15
app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart

@@ -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) {
-    //   return InkWell(
-    //     onTap: onPressed,
-    //     child: FlowyHover(
-    //       config: HoverDisplayConfig(borderRadius: radius ?? BorderRadius.circular(6), hoverColor: hoverColor!),
-    //       builder: (context, onHover) => child,
-    //     ),
-    //   );
-    // } else {
-    //   return InkWell(
-    //     onTap: onPressed,
-    //     child: child,
-    //   );
-    // }
+    if (tooltip != null) {
+      child = Tooltip(
+        message: tooltip!,
+        child: child,
+      );
+    }
+
+    return child;
   }
   }
 }
 }
 // return TextButton(
 // return TextButton(

+ 42 - 0
app_flowy/pubspec.lock

@@ -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:

+ 1 - 0
app_flowy/pubspec.yaml

@@ -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.