Ver Fonte

feat: support windows supabase deeplink callback (#3133)

Lucas.Xu há 1 ano atrás
pai
commit
95a938657b

+ 24 - 2
frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart

@@ -1,15 +1,29 @@
+import 'dart:async';
+import 'dart:io';
+
 import 'package:appflowy/env/env.dart';
 import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
 import 'package:flutter/foundation.dart';
 import 'package:supabase_flutter/supabase_flutter.dart';
+import 'package:url_protocol/url_protocol.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:path/path.dart' as p;
-
 import '../startup.dart';
 
+// ONLY supports in macOS and Windows now.
+//
+// If you need to update the schema, please update the following files:
+// - appflowy_flutter/macos/Runner/Info.plist (macOS)
+// - the callback url in Supabase dashboard
+const appflowyDeepLinkSchema = 'appflowy-flutter';
+const supabaseLoginCallback = '$appflowyDeepLinkSchema://login-callback';
+
 bool isSupabaseInitialized = false;
 const hiveBoxName = 'appflowy_supabase_authentication';
 
+// Used to store the session of the supabase in case of the user switch the different folder.
+Supabase? supabase;
+
 class InitSupabaseTask extends LaunchTask {
   @override
   Future<void> initialize(LaunchContext context) async {
@@ -20,7 +34,15 @@ class InitSupabaseTask extends LaunchTask {
     if (isSupabaseInitialized) {
       return;
     }
-    await Supabase.initialize(
+
+    // register deep link for Windows
+    if (Platform.isWindows) {
+      registerProtocolHandler(appflowyDeepLinkSchema);
+    }
+
+    supabase?.dispose();
+    supabase = null;
+    supabase = await Supabase.initialize(
       url: Env.supabaseUrl,
       anonKey: Env.supabaseAnonKey,
       debug: kDebugMode,

+ 3 - 5
frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart

@@ -1,6 +1,7 @@
 import 'dart:async';
 
 import 'package:appflowy/env/env.dart';
+import 'package:appflowy/startup/tasks/prelude.dart';
 import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
 import 'package:appflowy/user/application/auth/auth_service.dart';
 import 'package:appflowy/user/application/user_service.dart';
@@ -13,9 +14,6 @@ import 'package:flutter/foundation.dart';
 import 'package:supabase_flutter/supabase_flutter.dart';
 import 'auth_error.dart';
 
-// can't use underscore here.
-const loginCallback = 'io.appflowy.appflowy-flutter://login-callback';
-
 class SupabaseAuthService implements AuthService {
   SupabaseAuthService();
 
@@ -123,7 +121,7 @@ class SupabaseAuthService implements AuthService {
     final response = await _auth.signInWithOAuth(
       provider,
       queryParams: queryParamsForProvider(provider),
-      redirectTo: loginCallback,
+      redirectTo: supabaseLoginCallback,
     );
     if (!response) {
       completer.complete(left(AuthError.supabaseSignInWithOauthError));
@@ -171,7 +169,7 @@ class SupabaseAuthService implements AuthService {
 
     await _auth.signInWithOtp(
       email: email,
-      emailRedirectTo: kIsWeb ? null : loginCallback,
+      emailRedirectTo: kIsWeb ? null : supabaseLoginCallback,
     );
     return completer.future;
   }

+ 1 - 1
frontend/appflowy_flutter/macos/Runner/Info.plist

@@ -34,7 +34,7 @@
 			<string></string>
 			<key>CFBundleURLSchemes</key>
 			<array>
-				<string>io.appflowy.appflowy-flutter</string>
+				<string>appflowy-flutter</string>
 			</array>
 		</dict>
 	</array>

+ 9 - 0
frontend/appflowy_flutter/pubspec.lock

@@ -1589,6 +1589,15 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "3.0.6"
+  url_protocol:
+    dependency: "direct main"
+    description:
+      path: "."
+      ref: HEAD
+      resolved-ref: "77a84201ed8ca50082f4248f3a373d053b1c0462"
+      url: "https://github.com/LucasXu0/flutter_url_protocol.git"
+    source: git
+    version: "1.0.0"
   uuid:
     dependency: transitive
     description:

+ 4 - 0
frontend/appflowy_flutter/pubspec.yaml

@@ -98,6 +98,10 @@ dependencies:
   supabase_flutter: ^1.10.4
   envied: ^0.3.0+3
   dotted_border: ^2.0.0+3
+  url_protocol:
+    git:
+      url: https://github.com/LucasXu0/flutter_url_protocol.git
+      commit: 77a8420
   hive: ^2.2.3
   hive_flutter: ^1.1.0
 

+ 0 - 11
frontend/appflowy_flutter/windows/runner/main.cpp

@@ -8,16 +8,6 @@
 int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
                       _In_ wchar_t *command_line, _In_ int show_command)
 {
-  HANDLE hMutexInstance = CreateMutex(NULL, TRUE, L"AppFlowyMutex");
-  HWND handle = FindWindowA(NULL, "AppFlowy");
-
-  if (GetLastError() == ERROR_ALREADY_EXISTS) {
-    WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) };
-    GetWindowPlacement(handle, &place);
-    ShowWindow(handle, SW_NORMAL);
-    return 0;
-  }
-
   // Attach to console when present (e.g., 'flutter run') or create a
   // new console when running with a debugger.
   if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent())
@@ -53,6 +43,5 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
   }
 
   ::CoUninitialize();
-  ReleaseMutex(hMutexInstance);
   return EXIT_SUCCESS;
 }

+ 170 - 90
frontend/appflowy_flutter/windows/runner/win32_window.cpp

@@ -4,47 +4,57 @@
 
 #include "resource.h"
 
-namespace {
+#include "app_links/app_links_plugin_c_api.h"
 
-constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+namespace
+{
 
-// The number of Win32Window objects that currently exist.
-static int g_active_window_count = 0;
+  constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
 
-using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+  // The number of Win32Window objects that currently exist.
+  static int g_active_window_count = 0;
 
-// Scale helper to convert logical scaler values to physical using passed in
-// scale factor
-int Scale(int source, double scale_factor) {
-  return static_cast<int>(source * scale_factor);
-}
+  using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
 
-// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
-// This API is only needed for PerMonitor V1 awareness mode.
-void EnableFullDpiSupportIfAvailable(HWND hwnd) {
-  HMODULE user32_module = LoadLibraryA("User32.dll");
-  if (!user32_module) {
-    return;
+  // Scale helper to convert logical scaler values to physical using passed in
+  // scale factor
+  int Scale(int source, double scale_factor)
+  {
+    return static_cast<int>(source * scale_factor);
   }
-  auto enable_non_client_dpi_scaling =
-      reinterpret_cast<EnableNonClientDpiScaling*>(
-          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
-  if (enable_non_client_dpi_scaling != nullptr) {
-    enable_non_client_dpi_scaling(hwnd);
-    FreeLibrary(user32_module);
+
+  // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+  // This API is only needed for PerMonitor V1 awareness mode.
+  void EnableFullDpiSupportIfAvailable(HWND hwnd)
+  {
+    HMODULE user32_module = LoadLibraryA("User32.dll");
+    if (!user32_module)
+    {
+      return;
+    }
+    auto enable_non_client_dpi_scaling =
+        reinterpret_cast<EnableNonClientDpiScaling *>(
+            GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+    if (enable_non_client_dpi_scaling != nullptr)
+    {
+      enable_non_client_dpi_scaling(hwnd);
+      FreeLibrary(user32_module);
+    }
   }
-}
 
-}  // namespace
+} // namespace
 
 // Manages the Win32Window's window class registration.
-class WindowClassRegistrar {
- public:
+class WindowClassRegistrar
+{
+public:
   ~WindowClassRegistrar() = default;
 
   // Returns the singleton registar instance.
-  static WindowClassRegistrar* GetInstance() {
-    if (!instance_) {
+  static WindowClassRegistrar *GetInstance()
+  {
+    if (!instance_)
+    {
       instance_ = new WindowClassRegistrar();
     }
     return instance_;
@@ -52,24 +62,26 @@ class WindowClassRegistrar {
 
   // Returns the name of the window class, registering the class if it hasn't
   // previously been registered.
-  const wchar_t* GetWindowClass();
+  const wchar_t *GetWindowClass();
 
   // Unregisters the window class. Should only be called if there are no
   // instances of the window.
   void UnregisterWindowClass();
 
- private:
+private:
   WindowClassRegistrar() = default;
 
-  static WindowClassRegistrar* instance_;
+  static WindowClassRegistrar *instance_;
 
   bool class_registered_ = false;
 };
 
-WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr;
 
-const wchar_t* WindowClassRegistrar::GetWindowClass() {
-  if (!class_registered_) {
+const wchar_t *WindowClassRegistrar::GetWindowClass()
+{
+  if (!class_registered_)
+  {
     WNDCLASS window_class{};
     window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
     window_class.lpszClassName = kWindowClassName;
@@ -88,26 +100,35 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() {
   return kWindowClassName;
 }
 
-void WindowClassRegistrar::UnregisterWindowClass() {
+void WindowClassRegistrar::UnregisterWindowClass()
+{
   UnregisterClass(kWindowClassName, nullptr);
   class_registered_ = false;
 }
 
-Win32Window::Win32Window() {
+Win32Window::Win32Window()
+{
   ++g_active_window_count;
 }
 
-Win32Window::~Win32Window() {
+Win32Window::~Win32Window()
+{
   --g_active_window_count;
   Destroy();
 }
 
-bool Win32Window::CreateAndShow(const std::wstring& title,
-                                const Point& origin,
-                                const Size& size) {
+bool Win32Window::CreateAndShow(const std::wstring &title,
+                                const Point &origin,
+                                const Size &size)
+{
+  if (SendAppLinkToInstance(title))
+  {
+    return false;
+  }
+
   Destroy();
 
-  const wchar_t* window_class =
+  const wchar_t *window_class =
       WindowClassRegistrar::GetInstance()->GetWindowClass();
 
   const POINT target_point = {static_cast<LONG>(origin.x),
@@ -122,27 +143,69 @@ bool Win32Window::CreateAndShow(const std::wstring& title,
       Scale(size.width, scale_factor), Scale(size.height, scale_factor),
       nullptr, nullptr, GetModuleHandle(nullptr), this);
 
-  if (!window) {
+  if (!window)
+  {
     return false;
   }
 
   return OnCreate();
 }
 
+bool Win32Window::SendAppLinkToInstance(const std::wstring &title)
+{
+  // Find our exact window
+  HWND hwnd = ::FindWindow(kWindowClassName, title.c_str());
+
+  if (hwnd)
+  {
+    // Dispatch new link to current window
+    SendAppLink(hwnd);
+
+    // (Optional) Restore our window to front in same state
+    WINDOWPLACEMENT place = {sizeof(WINDOWPLACEMENT)};
+    GetWindowPlacement(hwnd, &place);
+
+    switch (place.showCmd)
+    {
+    case SW_SHOWMAXIMIZED:
+      ShowWindow(hwnd, SW_SHOWMAXIMIZED);
+      break;
+    case SW_SHOWMINIMIZED:
+      ShowWindow(hwnd, SW_RESTORE);
+      break;
+    default:
+      ShowWindow(hwnd, SW_NORMAL);
+      break;
+    }
+
+    SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
+    SetForegroundWindow(hwnd);
+
+    // Window has been found, don't create another one.
+    return true;
+  }
+
+  return false;
+}
+
 // static
 LRESULT CALLBACK Win32Window::WndProc(HWND const window,
                                       UINT const message,
                                       WPARAM const wparam,
-                                      LPARAM const lparam) noexcept {
-  if (message == WM_NCCREATE) {
-    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+                                      LPARAM const lparam) noexcept
+{
+  if (message == WM_NCCREATE)
+  {
+    auto window_struct = reinterpret_cast<CREATESTRUCT *>(lparam);
     SetWindowLongPtr(window, GWLP_USERDATA,
                      reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
 
-    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    auto that = static_cast<Win32Window *>(window_struct->lpCreateParams);
     EnableFullDpiSupportIfAvailable(window);
     that->window_handle_ = window;
-  } else if (Win32Window* that = GetThisFromHandle(window)) {
+  }
+  else if (Win32Window *that = GetThisFromHandle(window))
+  {
     return that->MessageHandler(window, message, wparam, lparam);
   }
 
@@ -153,64 +216,76 @@ LRESULT
 Win32Window::MessageHandler(HWND hwnd,
                             UINT const message,
                             WPARAM const wparam,
-                            LPARAM const lparam) noexcept {
-  switch (message) {
-    case WM_DESTROY:
-      window_handle_ = nullptr;
-      Destroy();
-      if (quit_on_close_) {
-        PostQuitMessage(0);
-      }
-      return 0;
-
-    case WM_DPICHANGED: {
-      auto newRectSize = reinterpret_cast<RECT*>(lparam);
-      LONG newWidth = newRectSize->right - newRectSize->left;
-      LONG newHeight = newRectSize->bottom - newRectSize->top;
-
-      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
-                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
-
-      return 0;
+                            LPARAM const lparam) noexcept
+{
+  switch (message)
+  {
+  case WM_DESTROY:
+    window_handle_ = nullptr;
+    Destroy();
+    if (quit_on_close_)
+    {
+      PostQuitMessage(0);
     }
-    case WM_SIZE: {
-      RECT rect = GetClientArea();
-      if (child_content_ != nullptr) {
-        // Size and position the child window.
-        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
-                   rect.bottom - rect.top, TRUE);
-      }
-      return 0;
+    return 0;
+
+  case WM_DPICHANGED:
+  {
+    auto newRectSize = reinterpret_cast<RECT *>(lparam);
+    LONG newWidth = newRectSize->right - newRectSize->left;
+    LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+    SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                 newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+    return 0;
+  }
+  case WM_SIZE:
+  {
+    RECT rect = GetClientArea();
+    if (child_content_ != nullptr)
+    {
+      // Size and position the child window.
+      MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                 rect.bottom - rect.top, TRUE);
     }
+    return 0;
+  }
 
-    case WM_ACTIVATE:
-      if (child_content_ != nullptr) {
-        SetFocus(child_content_);
-      }
-      return 0;
+  case WM_ACTIVATE:
+    if (child_content_ != nullptr)
+    {
+      SetFocus(child_content_);
+    }
+    return 0;
   }
 
   return DefWindowProc(window_handle_, message, wparam, lparam);
 }
 
-void Win32Window::Destroy() {
+void Win32Window::Destroy()
+{
   OnDestroy();
 
-  if (window_handle_) {
+  if (window_handle_)
+  {
     DestroyWindow(window_handle_);
     window_handle_ = nullptr;
   }
-  if (g_active_window_count == 0) {
+  if (g_active_window_count == 0)
+  {
     WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
   }
 }
 
-Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
-  return reinterpret_cast<Win32Window*>(
+Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept
+{
+  return reinterpret_cast<Win32Window *>(
       GetWindowLongPtr(window, GWLP_USERDATA));
 }
 
-void Win32Window::SetChildContent(HWND content) {
+void Win32Window::SetChildContent(HWND content)
+{
   child_content_ = content;
   SetParent(content, window_handle_);
   RECT frame = GetClientArea();
@@ -221,25 +296,30 @@ void Win32Window::SetChildContent(HWND content) {
   SetFocus(child_content_);
 }
 
-RECT Win32Window::GetClientArea() {
+RECT Win32Window::GetClientArea()
+{
   RECT frame;
   GetClientRect(window_handle_, &frame);
   return frame;
 }
 
-HWND Win32Window::GetHandle() {
+HWND Win32Window::GetHandle()
+{
   return window_handle_;
 }
 
-void Win32Window::SetQuitOnClose(bool quit_on_close) {
+void Win32Window::SetQuitOnClose(bool quit_on_close)
+{
   quit_on_close_ = quit_on_close;
 }
 
-bool Win32Window::OnCreate() {
+bool Win32Window::OnCreate()
+{
   // No-op; provided for subclasses.
   return true;
 }
 
-void Win32Window::OnDestroy() {
+void Win32Window::OnDestroy()
+{
   // No-op; provided for subclasses.
 }

+ 18 - 11
frontend/appflowy_flutter/windows/runner/win32_window.h

@@ -10,15 +10,18 @@
 // A class abstraction for a high DPI-aware Win32 Window. Intended to be
 // inherited from by classes that wish to specialize with custom
 // rendering and input handling
-class Win32Window {
- public:
-  struct Point {
+class Win32Window
+{
+public:
+  struct Point
+  {
     unsigned int x;
     unsigned int y;
     Point(unsigned int x, unsigned int y) : x(x), y(y) {}
   };
 
-  struct Size {
+  struct Size
+  {
     unsigned int width;
     unsigned int height;
     Size(unsigned int width, unsigned int height)
@@ -34,9 +37,9 @@ class Win32Window {
   // consistent size to will treat the width height passed in to this function
   // as logical pixels and scale to appropriate for the default monitor. Returns
   // true if the window was created successfully.
-  bool CreateAndShow(const std::wstring& title,
-                     const Point& origin,
-                     const Size& size);
+  bool CreateAndShow(const std::wstring &title,
+                     const Point &origin,
+                     const Size &size);
 
   // Release OS resources associated with window.
   void Destroy();
@@ -54,7 +57,7 @@ class Win32Window {
   // Return a RECT representing the bounds of the current client area.
   RECT GetClientArea();
 
- protected:
+protected:
   // Processes and route salient window messages for mouse handling,
   // size change and DPI. Delegates handling of these to member overloads that
   // inheriting classes can handle.
@@ -70,7 +73,7 @@ class Win32Window {
   // Called when Destroy is called.
   virtual void OnDestroy();
 
- private:
+private:
   friend class WindowClassRegistrar;
 
   // OS callback called by message pump. Handles the WM_NCCREATE message which
@@ -84,7 +87,11 @@ class Win32Window {
                                   LPARAM const lparam) noexcept;
 
   // Retrieves a class instance pointer for |window|
-  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+  static Win32Window *GetThisFromHandle(HWND const window) noexcept;
+
+  // Dispatches link if any.
+  // This method enables our app to be with a single instance too.
+  bool SendAppLinkToInstance(const std::wstring &title);
 
   bool quit_on_close_ = false;
 
@@ -95,4 +102,4 @@ class Win32Window {
   HWND child_content_ = nullptr;
 };
 
-#endif  // RUNNER_WIN32_WINDOW_H_
+#endif // RUNNER_WIN32_WINDOW_H_