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

Windows local notifications (#439) #492

Merged
merged 26 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d88e00b
Initial
krida2000 Jul 13, 2023
e2f59b8
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Jul 24, 2023
a43aa7e
Add Windows notifications
krida2000 Jul 25, 2023
7632413
Minor correction [skip ci]
krida2000 Jul 25, 2023
1172066
Comment some code [skip ci]
krida2000 Jul 25, 2023
79852dd
Implement local notifications
krida2000 Jul 26, 2023
4ce92a5
Fix fmt & analyzer
krida2000 Jul 27, 2023
9c9ab0b
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Aug 2, 2023
8ef5a51
Corrections
krida2000 Aug 3, 2023
6824566
Fix dead area in chat
krida2000 Aug 3, 2023
f0b768a
Add changelog
krida2000 Aug 3, 2023
5cbb228
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Aug 3, 2023
bb18bbf
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Aug 3, 2023
a3274f5
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Aug 7, 2023
b749758
Review correction
krida2000 Aug 7, 2023
eabbcbc
Move clsid to config
krida2000 Aug 7, 2023
eb22c9f
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
SleepySquash Aug 9, 2023
797be5e
Corrections
SleepySquash Aug 9, 2023
da8f258
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
SleepySquash Aug 9, 2023
848c5d2
FIx
SleepySquash Aug 9, 2023
91f04e7
Fix
SleepySquash Aug 9, 2023
5222150
Fix
SleepySquash Aug 9, 2023
24b7493
Fix `UnreadCounter` color
SleepySquash Aug 9, 2023
618a977
Corrections
krida2000 Aug 10, 2023
6442060
Minor correction
krida2000 Aug 10, 2023
73c3038
Merge remote-tracking branch 'origin/main' into 439-windows-local-not…
krida2000 Aug 16, 2023
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@ All user visible changes to this project will be documented in this file. This p

[Diff](/../../compare/v0.1.0-alpha.9.2...v0.1.0-alpha.10) | [Milestone](/../../milestone/8)

### Added

- Windows:
- Notifications. ([#492], [#439])

### Changed

- UI:
- Context menu with fading effect on desktop. ([#506])

[#439]: /../../issues/439
[#492]: /../../pull/492
[#506]: /../../pull/506


Expand Down
6 changes: 6 additions & 0 deletions assets/conf.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,9 @@
#
# Default:
# version = "$(git describe --tags --dirty)"

[windows]
# Unique identifier of Windows application.
#
# Default:
# clsid = "00000000-0000-0000-0000-000000000000"
Binary file added assets/icons/app_icon.ico
SleepySquash marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
7 changes: 7 additions & 0 deletions lib/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ class Config {
/// Version identifier of `User-Agent` header to put in network queries.
static String userAgentVersion = '';

/// Unique identifier of Windows application.
static late String clsid;

/// Initializes this [Config] by applying values from the following sources
/// (in the following order):
/// - default values;
Expand Down Expand Up @@ -119,6 +122,10 @@ class Config {
userAgentVersion =
version.isNotEmpty ? version : (Pubspec.ref ?? Pubspec.version);

clsid = const bool.hasEnvironment('SOCAPP_WINDOWS_CLSID')
? const String.fromEnvironment('SOCAPP_WINDOWS_CLSID')
: (document['windows']?['clsid'] ?? '');

origin = url;

// Change default values to browser's location on web platform.
Expand Down
56 changes: 52 additions & 4 deletions lib/domain/service/notification.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
// <https://www.gnu.org/licenses/agpl-3.0.html>.

import 'dart:async';
import 'dart:io';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:win_toast/win_toast.dart';
import 'package:window_manager/window_manager.dart';

import '/config.dart';
import '/routes.dart';
import '/util/audio_utils.dart';
import '/util/platform_utils.dart';
Expand Down Expand Up @@ -66,7 +71,30 @@ class NotificationService extends DisposableService {
// user's interaction.
WebUtils.onSelectNotification = onNotificationResponse;
} else {
if (_plugin == null) {
if (PlatformUtils.isWindows) {
await WinToast.instance().initialize(
aumId: 'team113.messenger',
displayName: 'Gapopa',
iconPath: kDebugMode
? File(r'assets\icons\app_icon.ico').absolute.path
: File(r'data\flutter_assets\assets\icons\app_icon.ico')
.absolute
.path,
clsid: Config.clsid,
);

WinToast.instance().setActivatedCallback((event) async {
await WindowManager.instance.focus();

onNotificationResponse?.call(
NotificationResponse(
notificationResponseType:
NotificationResponseType.selectedNotification,
payload: event.argument.isEmpty ? null : event.argument,
),
);
});
} else if (_plugin == null) {
_plugin = FlutterLocalNotificationsPlugin();
await _plugin!.initialize(
InitializationSettings(
Expand Down Expand Up @@ -118,9 +146,29 @@ class NotificationService extends DisposableService {
icon: icon,
tag: tag,
).onError((_, __) => false);
} else if (!PlatformUtils.isWindows) {
// TODO: `flutter_local_notifications` should support Windows:
// https://github.com/MaikuB/flutter_local_notifications/issues/746
} else if (PlatformUtils.isWindows) {
// TODO: Images should be downloaded to cache.
final File? file = await PlatformUtils.download(
icon!,
'notification_${DateTime.now().toString().replaceAll(':', '.')}.jpg',
null,
temporary: true,
);

await WinToast.instance().showCustomToast(
xml: '<?xml version="1.0" encoding="UTF-8"?>'
'<toast activationType="Foreground" launch="${payload ?? ''}">'
' <visual addImageQuery="true">'
' <binding template="ToastGeneric">'
' <text>$title</text>'
' <text>${body ?? ''}</text>'
' <image placement="appLogoOverride" hint-crop="circle" id="1" src="${file?.path ?? ''}"/>'
' </binding>'
' </visual>'
'</toast>',
tag: 'Gapopa',
);
} else {
await _plugin!.show(
Random().nextInt(1 << 31),
title,
Expand Down
3 changes: 2 additions & 1 deletion lib/ui/page/home/page/chat/view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,8 @@ class _ChatViewState extends State<ChatView>
(previous is ChatForwardElement &&
previous.authorId == author &&
element.id.at.val.difference(previous.id.at.val).abs() <=
const Duration(minutes: 5));
const Duration(minutes: 5)) ||
previous is UnreadMessagesElement;
}
}

Expand Down
14 changes: 3 additions & 11 deletions lib/ui/page/home/page/chat/widget/chat_forward.dart
Original file line number Diff line number Diff line change
Expand Up @@ -518,17 +518,9 @@ class _ChatForwardWidgetState extends State<ChatForwardWidget> {
TextSpan(
children: [
if (text != null) text,
const WidgetSpan(child: SizedBox(width: 4)),
WidgetSpan(
child: Opacity(
opacity: 0,
child: MessageTimestamp(
at: quote.at,
date: true,
fontSize: style.fonts.labelSmall.fontSize,
),
),
),
// TODO: Use transparent [MessageTimestamp]:
// https://github.com/flutter/flutter/issues/124787
const WidgetSpan(child: SizedBox(width: 95)),
],
),
selectable: PlatformUtils.isDesktop || menu,
Expand Down
48 changes: 26 additions & 22 deletions lib/ui/page/home/page/chat/widget/time_label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,33 @@ class TimeLabelWidget extends StatelessWidget {
Widget build(BuildContext context) {
final Style style = Theme.of(context).extension<Style>()!;

return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: SwipeableStatus(
animation: animation,
padding: const EdgeInsets.only(right: 8),
crossAxisAlignment: CrossAxisAlignment.center,
swipeable: Padding(
padding: const EdgeInsets.only(right: 4),
child: Text(time.yyMd),
),
child: AnimatedOpacity(
key: Key('$time'),
opacity: opacity,
duration: const Duration(milliseconds: 250),
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: style.systemMessageBorder,
color: style.systemMessageColor,
return IgnorePointer(
ignoring: true,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: SwipeableStatus(
animation: animation,
padding: const EdgeInsets.only(right: 8),
crossAxisAlignment: CrossAxisAlignment.center,
swipeable: Padding(
padding: const EdgeInsets.only(right: 4),
child: Text(time.yyMd),
),
child: AnimatedOpacity(
key: Key('$time'),
opacity: opacity,
duration: const Duration(milliseconds: 250),
child: Center(
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: style.systemMessageBorder,
color: style.systemMessageColor,
),
child: Text(time.toRelative(), style: style.systemMessageStyle),
),
child: Text(time.toRelative(), style: style.systemMessageStyle),
),
),
),
Expand Down
28 changes: 25 additions & 3 deletions lib/util/platform_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ class PlatformUtilsImpl {
/// Path to the downloads directory.
String? _downloadDirectory;

/// Path to the temporary directory.
String? _temporaryDirectory;

/// `User-Agent` header to put in the network requests.
String? _userAgent;

Expand Down Expand Up @@ -272,6 +275,16 @@ class PlatformUtilsImpl {
/// Indicates whether the application is in active state.
Future<bool> get isActive async => _isActive && await isFocused;

/// Returns a path to the temporary directory.
Future<String> get temporaryDirectory async {
if (_temporaryDirectory != null) {
return _temporaryDirectory!;
}

_temporaryDirectory = (await getTemporaryDirectory()).path;
return _temporaryDirectory!;
}

/// Enters fullscreen mode.
Future<void> enterFullscreen() async {
if (isWeb) {
Expand Down Expand Up @@ -314,13 +327,15 @@ class PlatformUtilsImpl {
String filename, {
int? size,
String? url,
bool temporary = false,
}) async {
if ((size != null || url != null) && !PlatformUtils.isWeb) {
size = size ??
int.parse(((await (await dio).head(url!)).headers['content-length']
as List<String>)[0]);

String downloads = await PlatformUtils.downloadsDirectory;
String downloads =
temporary ? await temporaryDirectory : await downloadsDirectory;
String name = p.basenameWithoutExtension(filename);
String ext = p.extension(filename);
File file = File('$downloads/$filename');
Expand All @@ -345,6 +360,7 @@ class PlatformUtilsImpl {
int? size, {
Function(int count, int total)? onReceiveProgress,
CancelToken? cancelToken,
bool temporary = false,
}) async {
dynamic completeWith;

Expand Down Expand Up @@ -378,7 +394,12 @@ class PlatformUtilsImpl {
file = await Backoff.run(
() async {
try {
return await fileExists(filename, size: size, url: url);
return await fileExists(
filename,
size: size,
url: url,
temporary: temporary,
);
} catch (e) {
onError(e);
}
Expand All @@ -391,7 +412,8 @@ class PlatformUtilsImpl {
if (file == null) {
final String name = p.basenameWithoutExtension(filename);
final String extension = p.extension(filename);
final String path = await downloadsDirectory;
final String path =
temporary ? await temporaryDirectory : await downloadsDirectory;

file = File('$path/$filename');
for (int i = 1; await file!.exists(); ++i) {
Expand Down
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win_toast:
dependency: "direct main"
description:
name: win_toast
sha256: "371d62b17b30489cad41e831e6340c2777202d2b4b2fd55a14ffc566b3df7518"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
window_manager:
dependency: "direct main"
description:
Expand Down
3 changes: 2 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ dependencies:
vibration: ^1.7.7
wakelock_plus: ^1.1.1
web_socket_channel: ^2.4.0
win32: ^5.0.5
win_toast: ^0.3.0
window_manager: ^0.3.5
windows_taskbar: ^1.1.1
win32: ^5.0.5

dev_dependencies:
artemis: ^7.13.0-beta.1
Expand Down
1 change: 1 addition & 0 deletions test/e2e/mock/platform_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class PlatformUtilsMock extends PlatformUtilsImpl {
int? size, {
Function(int count, int total)? onReceiveProgress,
CancelToken? cancelToken,
bool temporary = false,
}) async {
int total = 100;
for (int count = 0; count <= total; count++) {
Expand Down
8 changes: 7 additions & 1 deletion test/mock/platform_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import 'package:messenger/util/platform_utils.dart';
/// Mocked [PlatformUtilsImpl] to use in the tests.
class PlatformUtilsMock extends PlatformUtilsImpl {
@override
Future<File?> fileExists(String filename, {int? size, String? url}) async {
Future<File?> fileExists(
String filename, {
int? size,
String? url,
bool temporary = false,
}) async {
return null;
}

Expand All @@ -35,6 +40,7 @@ class PlatformUtilsMock extends PlatformUtilsImpl {
int? size, {
Function(int count, int total)? onReceiveProgress,
CancelToken? cancelToken,
bool temporary = false,
}) async =>
File('test/path');

Expand Down
3 changes: 3 additions & 0 deletions windows/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <sentry_flutter/sentry_flutter_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <win_toast/win_toast_plugin.h>
#include <window_manager/window_manager_plugin.h>
#include <windows_taskbar/windows_taskbar_plugin.h>

Expand Down Expand Up @@ -55,6 +56,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WinToastPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WinToastPlugin"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
WindowsTaskbarPluginRegisterWithRegistrar(
Expand Down
1 change: 1 addition & 0 deletions windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
sentry_flutter
share_plus
url_launcher_windows
win_toast
window_manager
windows_taskbar
)
Expand Down
Loading