add invite blocking support plus msc4494 support
To accomplish this I also reworked how account data is stored and added a method to fetch capabilities, and another method to set account data
This commit is contained in:
parent
115bec5f05
commit
f97c5548a3
11 changed files with 176 additions and 24 deletions
2
.gitmodules
vendored
2
.gitmodules
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
[submodule "gomuks"]
|
[submodule "gomuks"]
|
||||||
path = gomuks
|
path = gomuks
|
||||||
url = https://github.com/gomuks/gomuks
|
url = https://github.com/gomuks/gomuks
|
||||||
branch = main
|
branch = timedoutuk/versions-rpc
|
||||||
|
|
|
||||||
2
gomuks
2
gomuks
|
|
@ -1 +1 @@
|
||||||
Subproject commit 0f8fb261ab57977fa24a4f63b7aafbb9a4ff1b7b
|
Subproject commit d382c664a04f3f202b86f859d1868fd4654a39e8
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
|
import "dart:convert";
|
||||||
|
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:nexus/models/account_data.dart";
|
import "package:nexus/models/account_data.dart";
|
||||||
|
|
||||||
class AccountDataController extends Notifier<IMap<String, AccountData>> {
|
class AccountDataController extends Notifier<AccountData> {
|
||||||
@override
|
@override
|
||||||
IMap<String, AccountData> build() => .new();
|
AccountData build() => .new();
|
||||||
|
|
||||||
void update(IMap<String, AccountData> newData) =>
|
void update(IMap<String, IMap<String, dynamic>> newAccountData) =>
|
||||||
state = .new({...state.unlock, ...newData.unlock});
|
state = .fromJson({
|
||||||
|
...json.decode(json.encode(state.toJson())),
|
||||||
|
...newAccountData
|
||||||
|
.map((key, value) => MapEntry(key, value["content"]))
|
||||||
|
.unlock,
|
||||||
|
});
|
||||||
|
|
||||||
static final provider =
|
static final provider = NotifierProvider<AccountDataController, AccountData>(
|
||||||
NotifierProvider<AccountDataController, IMap<String, AccountData>>(
|
AccountDataController.new,
|
||||||
AccountDataController.new,
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,12 @@ import "package:nexus/models/requests/redact_event.dart";
|
||||||
import "package:nexus/models/requests/report.dart";
|
import "package:nexus/models/requests/report.dart";
|
||||||
import "package:nexus/models/requests/send_event.dart";
|
import "package:nexus/models/requests/send_event.dart";
|
||||||
import "package:nexus/models/requests/send_message.dart";
|
import "package:nexus/models/requests/send_message.dart";
|
||||||
|
import "package:nexus/models/requests/set_account_data.dart";
|
||||||
import "package:nexus/models/requests/set_membership.dart";
|
import "package:nexus/models/requests/set_membership.dart";
|
||||||
import "package:nexus/models/requests/set_state.dart";
|
import "package:nexus/models/requests/set_state.dart";
|
||||||
import "package:nexus/models/requests/upload_media.dart";
|
import "package:nexus/models/requests/upload_media.dart";
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
|
import "package:nexus/models/spec_versions_response.dart";
|
||||||
import "package:nexus/models/sync_data.dart";
|
import "package:nexus/models/sync_data.dart";
|
||||||
import "package:nexus/src/third_party/gomuks.g.dart";
|
import "package:nexus/src/third_party/gomuks.g.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
|
|
@ -268,6 +270,9 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
Future<void> setMembership(SetMembershipRequest request) =>
|
Future<void> setMembership(SetMembershipRequest request) =>
|
||||||
_sendCommand("set_membership", request.toJson());
|
_sendCommand("set_membership", request.toJson());
|
||||||
|
|
||||||
|
Future<void> setAccountData(SetAccountDataRequest request) =>
|
||||||
|
_sendCommand("set_account_data", request.toJson());
|
||||||
|
|
||||||
Future<MessageContent> uploadMedia(UploadMediaRequest request) async =>
|
Future<MessageContent> uploadMedia(UploadMediaRequest request) async =>
|
||||||
.fromJson(await _sendCommand("upload_media", request.toJson()));
|
.fromJson(await _sendCommand("upload_media", request.toJson()));
|
||||||
|
|
||||||
|
|
@ -302,6 +307,9 @@ class ClientController extends AsyncNotifier<int> {
|
||||||
Future<void> exchangeToken(OAuthExchangeTokenRequest request) async =>
|
Future<void> exchangeToken(OAuthExchangeTokenRequest request) async =>
|
||||||
await _sendCommand("oauth_exchange_token", request.toJson());
|
await _sendCommand("oauth_exchange_token", request.toJson());
|
||||||
|
|
||||||
|
Future<SpecVersionsResponse> getSpecVersions() async =>
|
||||||
|
.fromJson(await _sendCommand("get_versions"));
|
||||||
|
|
||||||
Future<Uri?> discoverHomeserver(Uri homeserver) async {
|
Future<Uri?> discoverHomeserver(Uri homeserver) async {
|
||||||
try {
|
try {
|
||||||
final response = await _sendCommand("discover_homeserver", {
|
final response = await _sendCommand("discover_homeserver", {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:m3e_buttons/m3e_buttons.dart";
|
import "package:m3e_buttons/m3e_buttons.dart";
|
||||||
|
import "package:nexus/controllers/account_data.dart";
|
||||||
import "package:nexus/controllers/client.dart";
|
import "package:nexus/controllers/client.dart";
|
||||||
import "package:nexus/controllers/client_state.dart";
|
import "package:nexus/controllers/client_state.dart";
|
||||||
import "package:nexus/controllers/settings.dart";
|
import "package:nexus/controllers/settings.dart";
|
||||||
|
import "package:nexus/models/account_data.dart";
|
||||||
import "package:nexus/models/settings_category.dart";
|
import "package:nexus/models/settings_category.dart";
|
||||||
import "package:nexus/main.dart";
|
import "package:nexus/main.dart";
|
||||||
import "package:nexus/widgets/settings/dialog_list_tile.dart";
|
import "package:nexus/widgets/settings/dialog_list_tile.dart";
|
||||||
|
|
@ -16,6 +18,9 @@ class SettingsSectionsController
|
||||||
@override
|
@override
|
||||||
Future<IMap<String, IList<SettingsCategory>>> build() async {
|
Future<IMap<String, IList<SettingsCategory>>> build() async {
|
||||||
final settings = await ref.watch(SettingsController.provider.future);
|
final settings = await ref.watch(SettingsController.provider.future);
|
||||||
|
final specVersionsResponse = await ref
|
||||||
|
.watch(ClientController.provider.notifier)
|
||||||
|
.getSpecVersions();
|
||||||
|
|
||||||
return .new({
|
return .new({
|
||||||
"General": .new([
|
"General": .new([
|
||||||
|
|
@ -93,6 +98,51 @@ class SettingsSectionsController
|
||||||
if (ref.watch(ClientStateController.provider)?.isLoggedIn == true)
|
if (ref.watch(ClientStateController.provider)?.isLoggedIn == true)
|
||||||
"Account": .new([
|
"Account": .new([
|
||||||
.new(title: "Profile", icon: Icons.person, settings: .new([])),
|
.new(title: "Profile", icon: Icons.person, settings: .new([])),
|
||||||
|
.new(
|
||||||
|
title: "Safety",
|
||||||
|
icon: Icons.gpp_good,
|
||||||
|
settings: .new([
|
||||||
|
.new(
|
||||||
|
title: "Invite Blocking",
|
||||||
|
description:
|
||||||
|
"Block invites, either completely, or block only invites from users without shared private rooms (depends on server support).",
|
||||||
|
builder: (title, description, icon) => Consumer(
|
||||||
|
builder: (context, ref, _) =>
|
||||||
|
DialogListTile<DefaultInviteAction>(
|
||||||
|
icon: Icon(icon),
|
||||||
|
title: title,
|
||||||
|
subtitle: Text(description),
|
||||||
|
initialValue: ref
|
||||||
|
.watch(AccountDataController.provider)
|
||||||
|
.invitePermissionConfig
|
||||||
|
.defaultAction,
|
||||||
|
options: specVersionsResponse.unstableFeatures.msc4494
|
||||||
|
? DefaultInviteAction.values
|
||||||
|
: IList(
|
||||||
|
DefaultInviteAction.values,
|
||||||
|
).remove(.denyPublic).toList(),
|
||||||
|
getName: (option) => switch (option) {
|
||||||
|
.allow => "Allow",
|
||||||
|
.deny => "Deny",
|
||||||
|
.denyPublic => "Deny public",
|
||||||
|
},
|
||||||
|
onChanged: (value) => ref
|
||||||
|
.watch(ClientController.provider.notifier)
|
||||||
|
.setAccountData(
|
||||||
|
.new(
|
||||||
|
type: InvitePermissionConfig.key,
|
||||||
|
content: InvitePermissionConfig(
|
||||||
|
defaultAction: value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.onError(showError),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
icon: Icons.person_off,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
.new(
|
.new(
|
||||||
title: "Other",
|
title: "Other",
|
||||||
icon: Icons.key,
|
icon: Icons.key,
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,7 @@ class SpacesController extends Notifier<IList<Space>> {
|
||||||
],
|
],
|
||||||
}.nonNulls.toISet();
|
}.nonNulls.toISet();
|
||||||
|
|
||||||
final directMessages = IMap(
|
final directMessages = accountData.directMessages.values.flattened;
|
||||||
accountData["m.direct"]?.content ?? {},
|
|
||||||
).values.expand((e) => e).toISet();
|
|
||||||
|
|
||||||
final otherRooms = rooms.entries
|
final otherRooms = rooms.entries
|
||||||
.where(
|
.where(
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,69 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
part "account_data.freezed.dart";
|
part "account_data.freezed.dart";
|
||||||
part "account_data.g.dart";
|
part "account_data.g.dart";
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
abstract class AccountData with _$AccountData {
|
abstract class AccountData with _$AccountData {
|
||||||
|
const AccountData._();
|
||||||
|
static List<dynamic>? readRecentEmojiValue(
|
||||||
|
Map<dynamic, dynamic> json,
|
||||||
|
String key,
|
||||||
|
) => json[key]?["recent_emoji"];
|
||||||
|
|
||||||
|
static Map<String, List<dynamic>>? recentEmojiToJson(
|
||||||
|
IList<RecentEmoji> recentEmoji,
|
||||||
|
) => {"recent_emoji": recentEmoji.map((emoji) => emoji.toJson()).toList()};
|
||||||
|
|
||||||
const factory AccountData({
|
const factory AccountData({
|
||||||
required String userId,
|
@JsonKey(name: InvitePermissionConfig.key)
|
||||||
required String? roomId,
|
@Default(InvitePermissionConfig())
|
||||||
required String type,
|
InvitePermissionConfig invitePermissionConfig,
|
||||||
required dynamic content,
|
|
||||||
|
@JsonKey(name: "m.direct")
|
||||||
|
@Default(IMap.empty())
|
||||||
|
IMap<String, IList<String>> directMessages,
|
||||||
|
|
||||||
|
@JsonKey(
|
||||||
|
name: "m.recent_emoji",
|
||||||
|
readValue: AccountData.readRecentEmojiValue,
|
||||||
|
toJson: AccountData.recentEmojiToJson,
|
||||||
|
)
|
||||||
|
@Default(IList.empty())
|
||||||
|
IList<RecentEmoji> recentEmoji,
|
||||||
}) = _AccountData;
|
}) = _AccountData;
|
||||||
|
|
||||||
factory AccountData.fromJson(Map<String, Object?> json) =>
|
factory AccountData.fromJson(Map<String, Object?> json) =>
|
||||||
_$AccountDataFromJson(json);
|
_$AccountDataFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class InvitePermissionConfig with _$InvitePermissionConfig {
|
||||||
|
static const key = "m.invite_permission_config";
|
||||||
|
|
||||||
|
const factory InvitePermissionConfig({
|
||||||
|
@JsonKey(unknownEnumValue: DefaultInviteAction.allow)
|
||||||
|
@Default(DefaultInviteAction.allow)
|
||||||
|
DefaultInviteAction defaultAction,
|
||||||
|
}) = _InvitePermissionConfig;
|
||||||
|
|
||||||
|
factory InvitePermissionConfig.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$InvitePermissionConfigFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class RecentEmoji with _$RecentEmoji {
|
||||||
|
const factory RecentEmoji({required String emoji, required int total}) =
|
||||||
|
_RecentEmoji;
|
||||||
|
|
||||||
|
factory RecentEmoji.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$RecentEmojiFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonEnum(fieldRename: .snake)
|
||||||
|
enum DefaultInviteAction {
|
||||||
|
allow,
|
||||||
|
deny,
|
||||||
|
@JsonValue("uk.timedout.msc4494.deny_public")
|
||||||
|
denyPublic,
|
||||||
|
}
|
||||||
|
|
|
||||||
15
lib/models/requests/set_account_data.dart
Normal file
15
lib/models/requests/set_account_data.dart
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
part "set_account_data.freezed.dart";
|
||||||
|
part "set_account_data.g.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SetAccountDataRequest with _$SetAccountDataRequest {
|
||||||
|
const factory SetAccountDataRequest({
|
||||||
|
required String type,
|
||||||
|
required dynamic content,
|
||||||
|
String? roomId,
|
||||||
|
}) = _SetAccountDataRequest;
|
||||||
|
|
||||||
|
factory SetAccountDataRequest.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SetAccountDataRequestFromJson(json);
|
||||||
|
}
|
||||||
25
lib/models/spec_versions_response.dart
Normal file
25
lib/models/spec_versions_response.dart
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
part "spec_versions_response.freezed.dart";
|
||||||
|
part "spec_versions_response.g.dart";
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class SpecVersionsResponse with _$SpecVersionsResponse {
|
||||||
|
const factory SpecVersionsResponse({
|
||||||
|
required IList<String> versions,
|
||||||
|
required UnstableFeatures unstableFeatures,
|
||||||
|
}) = _SpecVersionsResponse;
|
||||||
|
|
||||||
|
factory SpecVersionsResponse.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SpecVersionsResponseFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
abstract class UnstableFeatures with _$UnstableFeatures {
|
||||||
|
const factory UnstableFeatures({
|
||||||
|
@JsonKey(name: "uk.timedout.msc4494") @Default(false) bool msc4494,
|
||||||
|
}) = _UnstableFeatures;
|
||||||
|
|
||||||
|
factory UnstableFeatures.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$UnstableFeaturesFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:freezed_annotation/freezed_annotation.dart";
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
import "package:nexus/models/account_data.dart";
|
|
||||||
import "package:nexus/models/room.dart";
|
import "package:nexus/models/room.dart";
|
||||||
import "package:nexus/models/space_edge.dart";
|
import "package:nexus/models/space_edge.dart";
|
||||||
part "sync_data.freezed.dart";
|
part "sync_data.freezed.dart";
|
||||||
|
|
@ -10,7 +9,7 @@ part "sync_data.g.dart";
|
||||||
abstract class SyncData with _$SyncData {
|
abstract class SyncData with _$SyncData {
|
||||||
const factory SyncData({
|
const factory SyncData({
|
||||||
@Default(false) bool clearState,
|
@Default(false) bool clearState,
|
||||||
@Default(IMap.empty()) IMap<String, AccountData> accountData,
|
@Default(IMap.empty()) IMap<String, IMap<String, dynamic>> accountData,
|
||||||
@Default(IMap.empty()) IMap<String, Room> rooms,
|
@Default(IMap.empty()) IMap<String, Room> rooms,
|
||||||
@Default(ISet.empty()) ISet<String> leftRooms,
|
@Default(ISet.empty()) ISet<String> leftRooms,
|
||||||
// required IList<InvitedRoom> invitedRooms,
|
// required IList<InvitedRoom> invitedRooms,
|
||||||
|
|
|
||||||
|
|
@ -214,11 +214,9 @@ class RoomChat extends HookConsumerWidget {
|
||||||
...{
|
...{
|
||||||
...ref.watch(
|
...ref.watch(
|
||||||
AccountDataController.provider.select(
|
AccountDataController.provider.select(
|
||||||
(value) => IList(
|
(value) => value.recentEmoji
|
||||||
value["m.recent_emoji"]
|
.map((entry) => entry.emoji)
|
||||||
?.content["recent_emoji"] ??
|
.toIList(),
|
||||||
[],
|
|
||||||
).map((entry) => entry["emoji"]).toIList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
"👍",
|
"👍",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue