Skip to content

Commit

Permalink
Merge pull request #2029 from acterglobal/ben-desktop-tray
Browse files Browse the repository at this point in the history
Desktop backgrounding support
  • Loading branch information
gnunicorn authored Aug 16, 2024
2 parents fff0f39 + dd99604 commit b4f9310
Show file tree
Hide file tree
Showing 19 changed files with 348 additions and 14 deletions.
1 change: 1 addition & 0 deletions .changes/2029-backgrounding-and-tray-icon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Keep the app alive in background and close to the tray icon only (on supported platforms)
6 changes: 0 additions & 6 deletions app/assets/icon/comment.svg

This file was deleted.

Binary file added app/assets/icon/tray_icon.ico
Binary file not shown.
Binary file added app/assets/icon/tray_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions app/assets/icon/tray_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
Empty file added app/assets/videos/.gitkeep
Empty file.
Binary file removed app/assets/videos/video.mp4
Binary file not shown.
143 changes: 143 additions & 0 deletions app/lib/config/desktop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import 'dart:io';

import 'package:acter/common/utils/routes.dart';
import 'package:acter/router/router.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:tray_manager/tray_manager.dart';
import 'package:window_manager/window_manager.dart';
import 'package:logging/logging.dart';

final _log = Logger('a3::desktop');

class DesktopSupport extends StatefulWidget {
final Widget child;

const DesktopSupport({super.key, required this.child});

@override
State<DesktopSupport> createState() => _DesktopSupportState();
}

class _DesktopSupportState extends State<DesktopSupport>
with WindowListener, TrayListener {
@override
void initState() {
super.initState();
trayManager.addListener(this);
windowManager.addListener(this);
_initDesktop();
}

Future<void> _initDesktop() async {
// Must add this line.
await windowManager.ensureInitialized();

WindowOptions windowOptions = const WindowOptions(
title: 'Acter',
titleBarStyle: TitleBarStyle.normal,
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
await windowManager.setPreventClose(true);
});

await trayManager.setIcon(
Platform.isWindows
? 'assets/icon/tray_icon.ico'
: 'assets/icon/tray_icon.png',
);
Menu menu = Menu(
items: [
MenuItem(
key: 'home',
label: 'Home',
),
MenuItem(
key: 'chat',
label: 'Chat',
),
MenuItem(
key: 'activities',
label: 'Activities',
),
MenuItem.separator(),
MenuItem(
key: 'exit_app',
label: 'Exit App',
),
],
);
if (!Platform.isMacOS) {
// the menu crashes on macos if hidden for some reason.
await trayManager.setContextMenu(menu);
}
if (!Platform.isLinux) {
// not supported on linux;
await trayManager.setToolTip('Acter');
}
}

@override
void dispose() {
windowManager.removeListener(this);
trayManager.removeListener(this);
super.dispose();
}

@override
Widget build(BuildContext context) {
return widget.child;
}

@override
void onWindowClose() async {
bool isPreventClose = await windowManager.isPreventClose();
if (isPreventClose) {
await windowManager.hide();
}
}

@override
void onTrayIconMouseDown() async {
// toggle visiblity
if (await windowManager.isVisible()) {
_log.info('hiding window on toggle');
await windowManager.hide();
} else {
_log.info('showing window on toggle');
await windowManager.show();
}
}

@override
void onTrayIconRightMouseDown() async {
// do something
await trayManager.popUpContextMenu();
}

@override
void onTrayMenuItemClick(MenuItem menuItem) async {
if (menuItem.key == 'exit_app') {
_log.info('exit app');
await windowManager.destroy();
return;
}

await windowManager.show();
WidgetsBinding.instance.addPostFrameCallback((Duration duration) async {
if (menuItem.key == 'home') {
_log.info('route home');
rootNavKey.currentContext!.pushNamed(Routes.main.name);
} else if (menuItem.key == 'chat') {
_log.info('route chat');
rootNavKey.currentContext!.pushNamed(Routes.chat.name);
} else if (menuItem.key == 'activities') {
_log.info('route activities');
rootNavKey.currentContext!.pushNamed(Routes.activities.name);
}
});
}
}
2 changes: 1 addition & 1 deletion app/lib/features/auth/pages/forgot_password.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class _AskForEmail extends StatelessWidget {
var screenHeight = MediaQuery.of(context).size.height;
var imageSize = screenHeight / 4;
return SvgPicture.asset(
'assets/icon/forgot_password.svg',
'assets/images/forgot_password.svg',
height: imageSize,
width: imageSize,
);
Expand Down
2 changes: 1 addition & 1 deletion app/lib/features/intro/pages/intro_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class IntroPage extends StatelessWidget {
// limit the to always show the button even if the keyboard is opened
final imageSize = MediaQuery.of(context).size.height / 5;
return Image.asset(
'assets/icon/intro.png',
'assets/images/intro.png',
height: imageSize,
width: imageSize,
);
Expand Down
6 changes: 6 additions & 0 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:async';

import 'package:acter/common/themes/app_theme.dart';
import 'package:acter/config/desktop.dart';
import 'package:acter/config/notifications/init.dart';
import 'package:acter/common/providers/app_state_provider.dart';
import 'package:acter/common/themes/acter_theme.dart';
Expand Down Expand Up @@ -55,6 +57,10 @@ Future<void> _startAppInner(Widget app, bool withSentry) async {
await initLogging();
final initialLocationFromNotification = await initializeNotifications();

if (isDesktop) {
app = DesktopSupport(child: app);
}

if (initialLocationFromNotification != null) {
WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
// push after the next render to ensure we still have the "initial" location
Expand Down
14 changes: 10 additions & 4 deletions app/linux/my_application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
GtkWindow* window =
GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
GList *list = gtk_application_get_windows(GTK_APPLICATION(application));
GtkWindow* existing_window = list ? GTK_WINDOW(list->data) : NULL;

if (existing_window) {
gtk_window_present(existing_window);
return;
}

GtkWindow* window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));

// Use a header bar when running in GNOME as this is the common style used
// by applications and is the setup most users will be using (e.g. Ubuntu
Expand Down Expand Up @@ -99,6 +106,5 @@ static void my_application_init(MyApplication* self) {}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID,
"flags", G_APPLICATION_NON_UNIQUE,
nullptr));
"flags", nullptr));
}
Loading

0 comments on commit b4f9310

Please sign in to comment.