Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Desktop backgrounding support #2029

Merged
merged 20 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading