diff --git a/assets/images/ic_thumbs_up.svg b/assets/images/ic_thumbs_up.svg
new file mode 100644
index 0000000000..6783c7c53f
--- /dev/null
+++ b/assets/images/ic_thumbs_up.svg
@@ -0,0 +1,3 @@
+
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index ce676362a1..798ae8d4e6 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -263,6 +263,7 @@ class ImagePaths {
String get icUser => _getImagePath('ic_user.svg');
String get icTag => _getImagePath('ic_tag.svg');
String get icColorPicker => _getImagePath('ic_color_picker.svg');
+ String get icThumbsUp => _getImagePath('ic_thumbs_up.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/labels/lib/converter/keyword_identifier_nullable_converter.dart b/labels/lib/converter/keyword_identifier_nullable_converter.dart
new file mode 100644
index 0000000000..7c86f9f6b2
--- /dev/null
+++ b/labels/lib/converter/keyword_identifier_nullable_converter.dart
@@ -0,0 +1,14 @@
+import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+class KeywordIdentifierNullableConverter
+ implements JsonConverter {
+ const KeywordIdentifierNullableConverter();
+
+ @override
+ KeyWordIdentifier? fromJson(String? json) =>
+ json != null ? KeyWordIdentifier(json) : null;
+
+ @override
+ String? toJson(KeyWordIdentifier? object) => object?.value;
+}
diff --git a/labels/lib/extensions/label_extension.dart b/labels/lib/extensions/label_extension.dart
index 91becde419..49e3c4a7c0 100644
--- a/labels/lib/extensions/label_extension.dart
+++ b/labels/lib/extensions/label_extension.dart
@@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/extensions/hex_color_extension.dart';
import 'package:flutter/material.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
+import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:labels/model/hex_color.dart';
import 'package:labels/model/label.dart';
@@ -28,7 +29,7 @@ extension LabelExtension on Label {
Label copyWith({
Id? id,
- String? keyword,
+ KeyWordIdentifier? keyword,
String? displayName,
HexColor? color,
}) {
diff --git a/labels/lib/model/label.dart b/labels/lib/model/label.dart
index 3189cdf199..6eed79996d 100644
--- a/labels/lib/model/label.dart
+++ b/labels/lib/model/label.dart
@@ -1,8 +1,10 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/http/converter/id_converter.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
+import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:labels/converter/hex_color_nullable_converter.dart';
+import 'package:labels/converter/keyword_identifier_nullable_converter.dart';
import 'package:labels/model/hex_color.dart';
part 'label.g.dart';
@@ -13,11 +15,12 @@ part 'label.g.dart';
converters: [
IdConverter(),
HexColorNullableConverter(),
+ KeywordIdentifierNullableConverter(),
],
)
class Label with EquatableMixin {
final Id? id;
- final String? keyword;
+ final KeyWordIdentifier? keyword;
final String? displayName;
final HexColor? color;
diff --git a/labels/test/method/get/get_label_method_test.dart b/labels/test/method/get/get_label_method_test.dart
index c9f36b6032..02e65d36aa 100644
--- a/labels/test/method/get/get_label_method_test.dart
+++ b/labels/test/method/get/get_label_method_test.dart
@@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
+import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:labels/labels.dart';
import '../method_fixtures.dart';
@@ -41,13 +42,13 @@ void main() {
final labelA = Label(
id: Id('A'),
- keyword: 'labelA',
+ keyword: KeyWordIdentifier('labelA'),
displayName: 'Label A',
color: HexColor('#111111'),
);
final labelB = Label(
id: Id('B'),
- keyword: 'labelB',
+ keyword: KeyWordIdentifier('labelB'),
displayName: 'Label B',
color: HexColor('#222222'),
);
@@ -205,7 +206,7 @@ void main() {
expect(parsed.notFound, isEmpty);
});
- test('should throw DioException when server returns 500', () async {
+ test('should throw DioError when server returns 500', () async {
// Arrange
final dio = createDio();
final adapter = DioAdapter(dio: dio);
diff --git a/labels/test/method/set/set_label_method_test.dart b/labels/test/method/set/set_label_method_test.dart
index 1b8e59eb5f..54c9dd2df3 100644
--- a/labels/test/method/set/set_label_method_test.dart
+++ b/labels/test/method/set/set_label_method_test.dart
@@ -86,7 +86,7 @@ void main() {
// Assert
expect(parsed, isNotNull);
- expect(parsed!.created![Id('4f29')]!.keyword, equals('important'));
+ expect(parsed!.created![Id('4f29')]!.keyword?.value, equals('important'));
});
test('should process multiple created labels', () async {
@@ -143,8 +143,8 @@ void main() {
// Assert
expect(parsed, isNotNull);
expect(parsed!.created!.length, equals(2));
- expect(parsed.created![Id('A')]!.keyword, equals('tagA'));
- expect(parsed.created![Id('B')]!.keyword, equals('tagB'));
+ expect(parsed.created![Id('A')]!.keyword?.value, equals('tagA'));
+ expect(parsed.created![Id('B')]!.keyword?.value, equals('tagB'));
});
test('should throw DioException when backend returns 500', () async {
diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart
index 6b108457cc..df37cc96a9 100644
--- a/lib/features/base/base_mailbox_controller.dart
+++ b/lib/features/base/base_mailbox_controller.dart
@@ -389,7 +389,7 @@ abstract class BaseMailboxController extends BaseController
);
final destinationMailbox = PlatformInfo.isWeb
- ? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments)
+ ? await DialogRouter().pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments)
: await push(AppRoutes.destinationPicker, arguments: arguments);
if (destinationMailbox is PresentationMailbox) {
@@ -658,7 +658,7 @@ abstract class BaseMailboxController extends BaseController
);
final destinationMailbox = PlatformInfo.isWeb
- ? await DialogRouter.pushGeneralDialog(
+ ? await DialogRouter().pushGeneralDialog(
routeName: AppRoutes.destinationPicker,
arguments: arguments,
)
diff --git a/lib/features/base/model/popup_menu_item_action.dart b/lib/features/base/model/popup_menu_item_action.dart
index 1cbffb5ba0..9e53516689 100644
--- a/lib/features/base/model/popup_menu_item_action.dart
+++ b/lib/features/base/model/popup_menu_item_action.dart
@@ -3,16 +3,23 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
typedef OnPopupMenuActionClick = void Function(PopupMenuItemAction action);
+typedef OnHoverShowSubmenu = void Function(GlobalKey key);
abstract class PopupMenuItemAction with EquatableMixin {
final T action;
final String? key;
final int category;
+ final Widget? submenu;
- PopupMenuItemAction(this.action, {this.key, this.category = -1});
+ PopupMenuItemAction(
+ this.action, {
+ this.key,
+ this.category = -1,
+ this.submenu,
+ });
@override
- List