refactor: Update to Dart 3.10 with . shorthands
This commit is contained in:
parent
75a37f3f7c
commit
1ea649f01e
167 changed files with 3351 additions and 3912 deletions
|
|
@ -7,9 +7,9 @@ class AccountBundles {
|
|||
AccountBundles({this.prefix, this.bundles});
|
||||
|
||||
AccountBundles.fromJson(Map<String, dynamic> json)
|
||||
: prefix = json.tryGet<String>('prefix'),
|
||||
bundles = json['bundles'] is List
|
||||
? json['bundles']
|
||||
: prefix = json.tryGet<String>('prefix'),
|
||||
bundles = json['bundles'] is List
|
||||
? json['bundles']
|
||||
.map((b) {
|
||||
try {
|
||||
return AccountBundle.fromJson(b);
|
||||
|
|
@ -19,13 +19,12 @@ class AccountBundles {
|
|||
})
|
||||
.whereType<AccountBundle>()
|
||||
.toList()
|
||||
: null;
|
||||
: null;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
if (prefix != null) 'prefix': prefix,
|
||||
if (bundles != null)
|
||||
'bundles': bundles!.map((v) => v.toJson()).toList(),
|
||||
};
|
||||
if (prefix != null) 'prefix': prefix,
|
||||
if (bundles != null) 'bundles': bundles!.map((v) => v.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
class AccountBundle {
|
||||
|
|
@ -35,13 +34,13 @@ class AccountBundle {
|
|||
AccountBundle({this.name, this.priority});
|
||||
|
||||
AccountBundle.fromJson(Map<String, dynamic> json)
|
||||
: name = json.tryGet<String>('name'),
|
||||
priority = json.tryGet<int>('priority');
|
||||
: name = json.tryGet<String>('name'),
|
||||
priority = json.tryGet<int>('priority');
|
||||
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
if (name != null) 'name': name,
|
||||
if (priority != null) 'priority': priority,
|
||||
};
|
||||
if (name != null) 'name': name,
|
||||
if (priority != null) 'priority': priority,
|
||||
};
|
||||
}
|
||||
|
||||
const accountBundlesType = 'im.fluffychat.account_bundles';
|
||||
|
|
@ -50,24 +49,21 @@ extension AccountBundlesExtension on Client {
|
|||
List<AccountBundle> get accountBundles {
|
||||
List<AccountBundle>? ret;
|
||||
if (accountData.containsKey(accountBundlesType)) {
|
||||
ret = AccountBundles.fromJson(accountData[accountBundlesType]!.content)
|
||||
.bundles;
|
||||
ret = AccountBundles.fromJson(
|
||||
accountData[accountBundlesType]!.content,
|
||||
).bundles;
|
||||
}
|
||||
ret ??= [];
|
||||
if (ret.isEmpty) {
|
||||
ret.add(
|
||||
AccountBundle(
|
||||
name: userID,
|
||||
priority: 0,
|
||||
),
|
||||
);
|
||||
ret.add(AccountBundle(name: userID, priority: 0));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Future<void> setAccountBundle(String name, [int? priority]) async {
|
||||
final data =
|
||||
AccountBundles.fromJson(accountData[accountBundlesType]?.content ?? {});
|
||||
final data = AccountBundles.fromJson(
|
||||
accountData[accountBundlesType]?.content ?? {},
|
||||
);
|
||||
var foundBundle = false;
|
||||
final bundles = data.bundles ??= [];
|
||||
for (final bundle in bundles) {
|
||||
|
|
@ -87,16 +83,18 @@ extension AccountBundlesExtension on Client {
|
|||
if (!accountData.containsKey(accountBundlesType)) {
|
||||
return; // nothing to do
|
||||
}
|
||||
final data =
|
||||
AccountBundles.fromJson(accountData[accountBundlesType]!.content);
|
||||
final data = AccountBundles.fromJson(
|
||||
accountData[accountBundlesType]!.content,
|
||||
);
|
||||
if (data.bundles == null) return;
|
||||
data.bundles!.removeWhere((b) => b.name == name);
|
||||
await setAccountData(userID!, accountBundlesType, data.toJson());
|
||||
}
|
||||
|
||||
String get sendPrefix {
|
||||
final data =
|
||||
AccountBundles.fromJson(accountData[accountBundlesType]?.content ?? {});
|
||||
final data = AccountBundles.fromJson(
|
||||
accountData[accountBundlesType]?.content ?? {},
|
||||
);
|
||||
return data.prefix!;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,11 @@ extension ApplicationAccountConfigExtension on Client {
|
|||
accountData[accountDataKey]?.content ?? {},
|
||||
);
|
||||
|
||||
Future<void> setApplicationAccountConfig(
|
||||
ApplicationAccountConfig config,
|
||||
) =>
|
||||
setAccountData(
|
||||
userID!,
|
||||
accountDataKey,
|
||||
config.toJson(),
|
||||
);
|
||||
Future<void> setApplicationAccountConfig(ApplicationAccountConfig config) =>
|
||||
setAccountData(userID!, accountDataKey, config.toJson());
|
||||
|
||||
/// Only updates the specified values in ApplicationAccountConfig
|
||||
Future<void> updateApplicationAccountConfig(
|
||||
ApplicationAccountConfig config,
|
||||
) {
|
||||
Future<void> updateApplicationAccountConfig(ApplicationAccountConfig config) {
|
||||
final currentConfig = applicationAccountConfig;
|
||||
return setAccountData(
|
||||
userID!,
|
||||
|
|
@ -57,14 +49,15 @@ class ApplicationAccountConfig {
|
|||
wallpaperUrl: json['wallpaper_url'] is String
|
||||
? Uri.tryParse(json['wallpaper_url'])
|
||||
: null,
|
||||
wallpaperOpacity:
|
||||
_sanitizedOpacity(json.tryGet<double>('wallpaper_opacity')),
|
||||
wallpaperOpacity: _sanitizedOpacity(
|
||||
json.tryGet<double>('wallpaper_opacity'),
|
||||
),
|
||||
wallpaperBlur: json.tryGet<double>('wallpaper_blur'),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'wallpaper_url': wallpaperUrl?.toString(),
|
||||
'wallpaper_opacity': wallpaperOpacity,
|
||||
'wallpaper_blur': wallpaperBlur,
|
||||
};
|
||||
'wallpaper_url': wallpaperUrl?.toString(),
|
||||
'wallpaper_opacity': wallpaperOpacity,
|
||||
'wallpaper_blur': wallpaperBlur,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ Future<T?> showAdaptiveBottomSheet<T>({
|
|||
builder: (context) => Center(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(16),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 480,
|
||||
maxHeight: 720,
|
||||
),
|
||||
constraints: const BoxConstraints(maxWidth: 480, maxHeight: 720),
|
||||
child: Material(
|
||||
elevation: Theme.of(context).dialogTheme.elevation ?? 4,
|
||||
shadowColor: Theme.of(context).dialogTheme.shadowColor,
|
||||
|
|
@ -42,11 +39,9 @@ Future<T?> showAdaptiveBottomSheet<T>({
|
|||
context: context,
|
||||
builder: (context) => ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.viewInsetsOf(context).bottom +
|
||||
min(
|
||||
MediaQuery.sizeOf(context).height - 32,
|
||||
600,
|
||||
),
|
||||
maxHeight:
|
||||
MediaQuery.viewInsetsOf(context).bottom +
|
||||
min(MediaQuery.sizeOf(context).height - 32, 600),
|
||||
),
|
||||
child: builder(context),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ class BackgroundPush {
|
|||
Future<void> loadLocale() async {
|
||||
final context = matrix?.context;
|
||||
// inspired by _lookupL10n in .dart_tool/flutter_gen/gen_l10n/l10n.dart
|
||||
l10n ??= (context != null ? L10n.of(context) : null) ??
|
||||
l10n ??=
|
||||
(context != null ? L10n.of(context) : null) ??
|
||||
(await L10n.delegate.load(PlatformDispatcher.instance.locale));
|
||||
}
|
||||
|
||||
|
|
@ -78,8 +79,26 @@ class BackgroundPush {
|
|||
void _init() async {
|
||||
//<GOOGLE_SERVICES>firebaseEnabled = true;
|
||||
try {
|
||||
mainIsolateReceivePort?.listen(
|
||||
(message) async {
|
||||
mainIsolateReceivePort?.listen((message) async {
|
||||
try {
|
||||
await notificationTap(
|
||||
NotificationResponseJson.fromJsonString(message),
|
||||
client: client,
|
||||
router: FluffyChatApp.router,
|
||||
l10n: l10n,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logs().wtf('Main Notification Tap crashed', e, s);
|
||||
}
|
||||
});
|
||||
if (PlatformInfos.isAndroid) {
|
||||
final port = ReceivePort();
|
||||
IsolateNameServer.removePortNameMapping('background_tab_port');
|
||||
IsolateNameServer.registerPortWithName(
|
||||
port.sendPort,
|
||||
'background_tab_port',
|
||||
);
|
||||
port.listen((message) async {
|
||||
try {
|
||||
await notificationTap(
|
||||
NotificationResponseJson.fromJsonString(message),
|
||||
|
|
@ -90,29 +109,7 @@ class BackgroundPush {
|
|||
} catch (e, s) {
|
||||
Logs().wtf('Main Notification Tap crashed', e, s);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (PlatformInfos.isAndroid) {
|
||||
final port = ReceivePort();
|
||||
IsolateNameServer.removePortNameMapping('background_tab_port');
|
||||
IsolateNameServer.registerPortWithName(
|
||||
port.sendPort,
|
||||
'background_tab_port',
|
||||
);
|
||||
port.listen(
|
||||
(message) async {
|
||||
try {
|
||||
await notificationTap(
|
||||
NotificationResponseJson.fromJsonString(message),
|
||||
client: client,
|
||||
router: FluffyChatApp.router,
|
||||
l10n: l10n,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logs().wtf('Main Notification Tap crashed', e, s);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
await _flutterLocalNotificationsPlugin.initialize(
|
||||
const InitializationSettings(
|
||||
|
|
@ -201,12 +198,14 @@ class BackgroundPush {
|
|||
if (PlatformInfos.isAndroid) {
|
||||
_flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.requestNotificationsPermission();
|
||||
}
|
||||
final clientName = PlatformInfos.clientName;
|
||||
oldTokens ??= <String>{};
|
||||
final pushers = await (client.getPushers().catchError((e) {
|
||||
final pushers =
|
||||
await (client.getPushers().catchError((e) {
|
||||
Logs().w('[Push] Unable to request pushers', e);
|
||||
return <Pusher>[];
|
||||
})) ??
|
||||
|
|
@ -235,10 +234,9 @@ class BackgroundPush {
|
|||
currentPushers.first.data.url.toString() == gatewayUrl &&
|
||||
currentPushers.first.data.format ==
|
||||
AppSettings.pushNotificationsPusherFormat.value &&
|
||||
mapEquals(
|
||||
currentPushers.single.data.additionalProperties,
|
||||
{"data_message": pusherDataMessageFormat},
|
||||
)) {
|
||||
mapEquals(currentPushers.single.data.additionalProperties, {
|
||||
"data_message": pusherDataMessageFormat,
|
||||
})) {
|
||||
Logs().i('[Push] Pusher already set');
|
||||
} else {
|
||||
Logs().i('Need to set new pusher');
|
||||
|
|
@ -290,8 +288,8 @@ class BackgroundPush {
|
|||
final pusherDataMessageFormat = Platform.isAndroid
|
||||
? 'android'
|
||||
: Platform.isIOS
|
||||
? 'ios'
|
||||
: null;
|
||||
? 'ios'
|
||||
: null;
|
||||
|
||||
static bool _wentToRoomOnStartup = false;
|
||||
|
||||
|
|
@ -315,9 +313,9 @@ class BackgroundPush {
|
|||
}
|
||||
|
||||
// ignore: unawaited_futures
|
||||
_flutterLocalNotificationsPlugin
|
||||
.getNotificationAppLaunchDetails()
|
||||
.then((details) {
|
||||
_flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails().then((
|
||||
details,
|
||||
) {
|
||||
if (details == null ||
|
||||
!details.didNotificationLaunchApp ||
|
||||
_wentToRoomOnStartup) {
|
||||
|
|
@ -348,9 +346,7 @@ class BackgroundPush {
|
|||
if (PlatformInfos.isAndroid) {
|
||||
onFcmError?.call(
|
||||
l10n!.noGoogleServicesWarning,
|
||||
link: Uri.parse(
|
||||
AppConfig.enablePushTutorial,
|
||||
),
|
||||
link: Uri.parse(AppConfig.enablePushTutorial),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -397,15 +393,13 @@ class BackgroundPush {
|
|||
'https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify';
|
||||
try {
|
||||
final url = Uri.parse(newEndpoint)
|
||||
.replace(
|
||||
path: '/_matrix/push/v1/notify',
|
||||
query: '',
|
||||
)
|
||||
.replace(path: '/_matrix/push/v1/notify', query: '')
|
||||
.toString()
|
||||
.split('?')
|
||||
.first;
|
||||
final res =
|
||||
json.decode(utf8.decode((await http.get(Uri.parse(url))).bodyBytes));
|
||||
final res = json.decode(
|
||||
utf8.decode((await http.get(Uri.parse(url))).bodyBytes),
|
||||
);
|
||||
if (res['gateway'] == 'matrix' ||
|
||||
(res['unifiedpush'] is Map &&
|
||||
res['unifiedpush']['gateway'] == 'matrix')) {
|
||||
|
|
@ -436,14 +430,13 @@ class BackgroundPush {
|
|||
upAction = true;
|
||||
Logs().i('[Push] Removing UnifiedPush endpoint...');
|
||||
final oldEndpoint = AppSettings.unifiedPushEndpoint.value;
|
||||
await AppSettings.unifiedPushEndpoint
|
||||
.setItem(AppSettings.unifiedPushEndpoint.defaultValue);
|
||||
await AppSettings.unifiedPushEndpoint.setItem(
|
||||
AppSettings.unifiedPushEndpoint.defaultValue,
|
||||
);
|
||||
await AppSettings.unifiedPushRegistered.setItem(false);
|
||||
if (oldEndpoint.isNotEmpty) {
|
||||
// remove the old pusher
|
||||
await setupPusher(
|
||||
oldTokens: {oldEndpoint},
|
||||
);
|
||||
await setupPusher(oldTokens: {oldEndpoint});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,9 @@ extension ClientDownloadContentExtension on Client {
|
|||
|
||||
final response = await httpClient.get(
|
||||
httpUri,
|
||||
headers:
|
||||
accessToken == null ? null : {'authorization': 'Bearer $accessToken'},
|
||||
headers: accessToken == null
|
||||
? null
|
||||
: {'authorization': 'Bearer $accessToken'},
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception();
|
||||
|
|
|
|||
|
|
@ -38,22 +38,27 @@ abstract class ClientManager {
|
|||
clientNames.add(PlatformInfos.clientName);
|
||||
await store.setStringList(clientNamespace, clientNames.toList());
|
||||
}
|
||||
final clients =
|
||||
await Future.wait(clientNames.map((name) => createClient(name, store)));
|
||||
final clients = await Future.wait(
|
||||
clientNames.map((name) => createClient(name, store)),
|
||||
);
|
||||
if (initialize) {
|
||||
await Future.wait(
|
||||
clients.map(
|
||||
(client) => client.initWithRestore(
|
||||
onMigration: () async {
|
||||
final l10n = await lookupL10n(PlatformDispatcher.instance.locale);
|
||||
sendInitNotification(
|
||||
l10n.databaseMigrationTitle,
|
||||
l10n.databaseMigrationBody,
|
||||
);
|
||||
},
|
||||
).catchError(
|
||||
(e, s) => Logs().e('Unable to initialize client', e, s),
|
||||
),
|
||||
(client) => client
|
||||
.initWithRestore(
|
||||
onMigration: () async {
|
||||
final l10n = await lookupL10n(
|
||||
PlatformDispatcher.instance.locale,
|
||||
);
|
||||
sendInitNotification(
|
||||
l10n.databaseMigrationTitle,
|
||||
l10n.databaseMigrationBody,
|
||||
);
|
||||
},
|
||||
)
|
||||
.catchError(
|
||||
(e, s) => Logs().e('Unable to initialize client', e, s),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -122,25 +127,26 @@ abstract class ClientManager {
|
|||
AuthenticationTypes.sso,
|
||||
},
|
||||
nativeImplementations: nativeImplementations,
|
||||
customImageResizer:
|
||||
PlatformInfos.isMobile || kIsWeb ? customImageResizer : null,
|
||||
customImageResizer: PlatformInfos.isMobile || kIsWeb
|
||||
? customImageResizer
|
||||
: null,
|
||||
defaultNetworkRequestTimeout: const Duration(minutes: 30),
|
||||
enableDehydratedDevices: true,
|
||||
shareKeysWith: ShareKeysWith.values
|
||||
.singleWhereOrNull((share) => share.name == shareKeysWith) ??
|
||||
shareKeysWith:
|
||||
ShareKeysWith.values.singleWhereOrNull(
|
||||
(share) => share.name == shareKeysWith,
|
||||
) ??
|
||||
ShareKeysWith.all,
|
||||
convertLinebreaksInFormatting: false,
|
||||
onSoftLogout:
|
||||
enableSoftLogout ? (client) => client.refreshAccessToken() : null,
|
||||
onSoftLogout: enableSoftLogout
|
||||
? (client) => client.refreshAccessToken()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
static void sendInitNotification(String title, String body) async {
|
||||
if (kIsWeb) {
|
||||
html.Notification(
|
||||
title,
|
||||
body: body,
|
||||
);
|
||||
html.Notification(title, body: body);
|
||||
return;
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
|
|
@ -148,9 +154,7 @@ abstract class ClientManager {
|
|||
title,
|
||||
body: body,
|
||||
appName: AppSettings.applicationName.value,
|
||||
hints: [
|
||||
NotificationHint.soundName('message-new-instant'),
|
||||
],
|
||||
hints: [NotificationHint.soundName('message-new-instant')],
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ class CustomHttpClient {
|
|||
}
|
||||
|
||||
static http.Client createHTTPClient() => retry.RetryClient(
|
||||
PlatformInfos.isAndroid
|
||||
? IOClient(customHttpClient(ISRG_X1))
|
||||
: http.Client(),
|
||||
);
|
||||
PlatformInfos.isAndroid
|
||||
? IOClient(customHttpClient(ISRG_X1))
|
||||
: http.Client(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,8 @@ Future<MatrixImageFileResizedResponse?> customImageResizer(
|
|||
// scale down image for blurhashing to speed it up
|
||||
final (blurW, blurH) = _scaleToBox(width, height, boxSize: 100);
|
||||
final blurhashImg = nativeImg.resample(
|
||||
blurW, blurH,
|
||||
blurW,
|
||||
blurH,
|
||||
// nearest is unsupported...
|
||||
native.Transform.bilinear,
|
||||
);
|
||||
|
|
@ -73,8 +74,11 @@ Future<MatrixImageFileResizedResponse?> customImageResizer(
|
|||
if (width > max || height > max) {
|
||||
(width, height) = _scaleToBox(width, height, boxSize: max);
|
||||
|
||||
final scaledImg =
|
||||
nativeImg.resample(width, height, native.Transform.lanczos);
|
||||
final scaledImg = nativeImg.resample(
|
||||
width,
|
||||
height,
|
||||
native.Transform.lanczos,
|
||||
);
|
||||
nativeImg.free();
|
||||
nativeImg = scaledImg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||
class CustomScrollBehavior extends MaterialScrollBehavior {
|
||||
@override
|
||||
Set<PointerDeviceKind> get dragDevices => {
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.trackpad,
|
||||
};
|
||||
PointerDeviceKind.touch,
|
||||
PointerDeviceKind.trackpad,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ extension DateTimeExtension on DateTime {
|
|||
|
||||
final sameDay = sameYear && now.month == month && now.day == day;
|
||||
|
||||
final sameWeek = sameYear &&
|
||||
final sameWeek =
|
||||
sameYear &&
|
||||
!sameDay &&
|
||||
now.millisecondsSinceEpoch - millisecondsSinceEpoch <
|
||||
1000 * 60 * 60 * 24 * 7;
|
||||
|
|
@ -50,14 +51,17 @@ extension DateTimeExtension on DateTime {
|
|||
if (sameDay) {
|
||||
return localizedTimeOfDay(context);
|
||||
} else if (sameWeek) {
|
||||
return DateFormat.E(Localizations.localeOf(context).languageCode)
|
||||
.format(this);
|
||||
return DateFormat.E(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(this);
|
||||
} else if (sameYear) {
|
||||
return DateFormat.MMMd(Localizations.localeOf(context).languageCode)
|
||||
.format(this);
|
||||
return DateFormat.MMMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(this);
|
||||
}
|
||||
return DateFormat.yMMMd(Localizations.localeOf(context).languageCode)
|
||||
.format(this);
|
||||
return DateFormat.yMMMd(
|
||||
Localizations.localeOf(context).languageCode,
|
||||
).format(this);
|
||||
}
|
||||
|
||||
/// If the DateTime is today, this returns [localizedTimeOfDay()], if not it also
|
||||
|
|
|
|||
|
|
@ -40,10 +40,7 @@ class ErrorReporter {
|
|||
child: SingleChildScrollView(
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'RobotoMono',
|
||||
),
|
||||
style: const TextStyle(fontSize: 14, fontFamily: 'RobotoMono'),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -53,9 +50,7 @@ class ErrorReporter {
|
|||
child: Text(L10n.of(context).close),
|
||||
),
|
||||
AdaptiveDialogAction(
|
||||
onPressed: () => Clipboard.setData(
|
||||
ClipboardData(text: text),
|
||||
),
|
||||
onPressed: () => Clipboard.setData(ClipboardData(text: text)),
|
||||
child: Text(L10n.of(context).copy),
|
||||
),
|
||||
AdaptiveDialogAction(
|
||||
|
|
|
|||
|
|
@ -6,18 +6,17 @@ extension EventCheckboxRoomExtension on Room {
|
|||
String eventId,
|
||||
int checkboxId, {
|
||||
String? txid,
|
||||
}) =>
|
||||
sendEvent(
|
||||
{
|
||||
'm.relates_to': {
|
||||
'rel_type': relationshipType,
|
||||
'event_id': eventId,
|
||||
'checkbox_id': checkboxId,
|
||||
},
|
||||
},
|
||||
type: EventTypes.Reaction,
|
||||
txid: txid,
|
||||
);
|
||||
}) => sendEvent(
|
||||
{
|
||||
'm.relates_to': {
|
||||
'rel_type': relationshipType,
|
||||
'event_id': eventId,
|
||||
'checkbox_id': checkboxId,
|
||||
},
|
||||
},
|
||||
type: EventTypes.Reaction,
|
||||
txid: txid,
|
||||
);
|
||||
}
|
||||
|
||||
extension EventCheckboxExtension on Event {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,10 @@ abstract class FluffyShare {
|
|||
);
|
||||
return;
|
||||
}
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: text),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).copiedToClipboard)),
|
||||
);
|
||||
await Clipboard.setData(ClipboardData(text: text));
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard)));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,22 +31,22 @@ class SessionBackup {
|
|||
SessionBackup.fromJson(jsonDecode(json));
|
||||
|
||||
factory SessionBackup.fromJson(Map<String, dynamic> json) => SessionBackup(
|
||||
olmAccount: json['olm_account'],
|
||||
accessToken: json['access_token'],
|
||||
userId: json['user_id'],
|
||||
homeserver: json['homeserver'],
|
||||
deviceId: json['device_id'],
|
||||
deviceName: json['device_name'],
|
||||
);
|
||||
olmAccount: json['olm_account'],
|
||||
accessToken: json['access_token'],
|
||||
userId: json['user_id'],
|
||||
homeserver: json['homeserver'],
|
||||
deviceId: json['device_id'],
|
||||
deviceName: json['device_name'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'olm_account': olmAccount,
|
||||
'access_token': accessToken,
|
||||
'user_id': userId,
|
||||
'homeserver': homeserver,
|
||||
'device_id': deviceId,
|
||||
if (deviceName != null) 'device_name': deviceName,
|
||||
};
|
||||
'olm_account': olmAccount,
|
||||
'access_token': accessToken,
|
||||
'user_id': userId,
|
||||
'homeserver': homeserver,
|
||||
'device_id': deviceId,
|
||||
if (deviceName != null) 'device_name': deviceName,
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() => jsonEncode(toJson());
|
||||
|
|
@ -82,7 +82,8 @@ extension InitWithRestoreExtension on Client {
|
|||
final homeserver = this.homeserver?.toString();
|
||||
final deviceId = deviceID;
|
||||
final userId = userID;
|
||||
final hasBackup = accessToken != null &&
|
||||
final hasBackup =
|
||||
accessToken != null &&
|
||||
homeserver != null &&
|
||||
deviceId != null &&
|
||||
userId != null;
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ extension LocalizedExceptionExtension on Object {
|
|||
final numString = round < 10
|
||||
? num.toStringAsFixed(2)
|
||||
: round < 100
|
||||
? num.toStringAsFixed(1)
|
||||
: round.toString();
|
||||
? num.toStringAsFixed(1)
|
||||
: round.toString();
|
||||
return '$numString ${'kMGTPEZY'[i - 1]}B';
|
||||
}
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ extension LocalizedExceptionExtension on Object {
|
|||
]) {
|
||||
if (this is FileTooBigMatrixException) {
|
||||
final exception = this as FileTooBigMatrixException;
|
||||
return L10n.of(context).fileIsTooBigForServer(
|
||||
_formatFileSize(exception.maxFileSize),
|
||||
);
|
||||
return L10n.of(
|
||||
context,
|
||||
).fileIsTooBigForServer(_formatFileSize(exception.maxFileSize));
|
||||
}
|
||||
if (this is OtherPartyCanNotReceiveMessages) {
|
||||
return L10n.of(context).otherPartyNotLoggedIn;
|
||||
|
|
|
|||
|
|
@ -64,8 +64,10 @@ Widget markdownContextBuilder(
|
|||
if (start == -1) start = 0;
|
||||
final end = selection.end;
|
||||
|
||||
final fullLineSelection =
|
||||
TextSelection(baseOffset: start, extentOffset: end);
|
||||
final fullLineSelection = TextSelection(
|
||||
baseOffset: start,
|
||||
extentOffset: end,
|
||||
);
|
||||
|
||||
const checkBox = '- [ ]';
|
||||
|
||||
|
|
@ -78,8 +80,11 @@ Widget markdownContextBuilder(
|
|||
: '$checkBox $line',
|
||||
)
|
||||
.join('\n');
|
||||
controller.text =
|
||||
controller.text.replaceRange(start, end, replacedRange);
|
||||
controller.text = controller.text.replaceRange(
|
||||
start,
|
||||
end,
|
||||
replacedRange,
|
||||
);
|
||||
ContextMenuController.removeAny();
|
||||
},
|
||||
),
|
||||
|
|
|
|||
|
|
@ -22,8 +22,13 @@ IconData _getIconFromName(String displayname) {
|
|||
}.any((s) => name.contains(s))) {
|
||||
return Icons.web_outlined;
|
||||
}
|
||||
if ({'desktop', 'windows', 'macos', 'linux', 'ubuntu'}
|
||||
.any((s) => name.contains(s))) {
|
||||
if ({
|
||||
'desktop',
|
||||
'windows',
|
||||
'macos',
|
||||
'linux',
|
||||
'ubuntu',
|
||||
}.any((s) => name.contains(s))) {
|
||||
return Icons.desktop_mac_outlined;
|
||||
}
|
||||
return Icons.device_unknown_outlined;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ extension LocalizedBody on Event {
|
|||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
futureWithProgress: (onProgress) {
|
||||
final fileSize =
|
||||
infoMap['size'] is int ? infoMap['size'] as int : null;
|
||||
final fileSize = infoMap['size'] is int
|
||||
? infoMap['size'] as int
|
||||
: null;
|
||||
return downloadAndDecryptAttachment(
|
||||
onDownloadProgress: fileSize == null
|
||||
? null
|
||||
|
|
@ -47,8 +48,11 @@ extension LocalizedBody on Event {
|
|||
thumbnailInfoMap['size'] < room.client.database.maxFileSize;
|
||||
|
||||
bool get showThumbnail =>
|
||||
[MessageTypes.Image, MessageTypes.Sticker, MessageTypes.Video]
|
||||
.contains(messageType) &&
|
||||
[
|
||||
MessageTypes.Image,
|
||||
MessageTypes.Sticker,
|
||||
MessageTypes.Video,
|
||||
].contains(messageType) &&
|
||||
(kIsWeb ||
|
||||
isAttachmentSmallEnough ||
|
||||
isThumbnailSmallEnough ||
|
||||
|
|
|
|||
|
|
@ -6,29 +6,28 @@ extension VisibleInGuiExtension on List<Event> {
|
|||
List<Event> filterByVisibleInGui({
|
||||
String? exceptionEventId,
|
||||
String? threadId,
|
||||
}) =>
|
||||
where(
|
||||
(event) {
|
||||
if (threadId != null &&
|
||||
event.relationshipType != RelationshipTypes.reaction) {
|
||||
if ((event.relationshipType != RelationshipTypes.thread ||
|
||||
event.relationshipEventId != threadId) &&
|
||||
event.eventId != threadId) {
|
||||
return false;
|
||||
}
|
||||
} else if (event.relationshipType == RelationshipTypes.thread) {
|
||||
return false;
|
||||
}
|
||||
return event.isVisibleInGui || event.eventId == exceptionEventId;
|
||||
},
|
||||
).toList();
|
||||
}) => where((event) {
|
||||
if (threadId != null &&
|
||||
event.relationshipType != RelationshipTypes.reaction) {
|
||||
if ((event.relationshipType != RelationshipTypes.thread ||
|
||||
event.relationshipEventId != threadId) &&
|
||||
event.eventId != threadId) {
|
||||
return false;
|
||||
}
|
||||
} else if (event.relationshipType == RelationshipTypes.thread) {
|
||||
return false;
|
||||
}
|
||||
return event.isVisibleInGui || event.eventId == exceptionEventId;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
extension IsStateExtension on Event {
|
||||
bool get isVisibleInGui =>
|
||||
// always filter out edit and reaction relationships
|
||||
!{RelationshipTypes.edit, RelationshipTypes.reaction}
|
||||
.contains(relationshipType) &&
|
||||
!{
|
||||
RelationshipTypes.edit,
|
||||
RelationshipTypes.reaction,
|
||||
}.contains(relationshipType) &&
|
||||
// always filter out m.key.* and other known but unimportant events
|
||||
!isKnownHiddenStates &&
|
||||
// event types to hide: redaction and reaction events
|
||||
|
|
@ -40,22 +39,20 @@ extension IsStateExtension on Event {
|
|||
(!AppSettings.hideUnknownEvents.value || isEventTypeKnown);
|
||||
|
||||
bool get isState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
}.contains(type);
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
}.contains(type);
|
||||
|
||||
bool get isCollapsedState => !{
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
EventTypes.RoomCreate,
|
||||
EventTypes.RoomTombstone,
|
||||
}.contains(type);
|
||||
EventTypes.Message,
|
||||
EventTypes.Sticker,
|
||||
EventTypes.Encrypted,
|
||||
EventTypes.RoomCreate,
|
||||
EventTypes.RoomTombstone,
|
||||
}.contains(type);
|
||||
|
||||
bool get isKnownHiddenStates =>
|
||||
{
|
||||
PollEventContent.responseType,
|
||||
}.contains(type) ||
|
||||
{PollEventContent.responseType}.contains(type) ||
|
||||
type.startsWith('m.key.verification.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,22 +28,19 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(String clientName) async {
|
|||
try {
|
||||
// Send error notification:
|
||||
final l10n = await lookupL10n(PlatformDispatcher.instance.locale);
|
||||
ClientManager.sendInitNotification(
|
||||
l10n.initAppError,
|
||||
e.toString(),
|
||||
);
|
||||
ClientManager.sendInitNotification(l10n.initAppError, e.toString());
|
||||
} catch (e, s) {
|
||||
Logs().e('Unable to send error notification', e, s);
|
||||
}
|
||||
|
||||
// Try to delete database so that it can created again on next init:
|
||||
database?.delete().catchError(
|
||||
(e, s) => Logs().wtf(
|
||||
'Unable to delete database, after failed construction',
|
||||
e,
|
||||
s,
|
||||
),
|
||||
);
|
||||
(e, s) => Logs().wtf(
|
||||
'Unable to delete database, after failed construction',
|
||||
e,
|
||||
s,
|
||||
),
|
||||
);
|
||||
|
||||
// Delete database file:
|
||||
if (!kIsWeb) {
|
||||
|
|
@ -77,8 +74,9 @@ Future<MatrixSdkDatabase> _constructDatabase(String clientName) async {
|
|||
// fix dlopen for old Android
|
||||
await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
|
||||
// import the SQLite / SQLCipher shared objects / dynamic libraries
|
||||
final factory =
|
||||
createDatabaseFactoryFfi(ffiInit: SQfLiteEncryptionHelper.ffiInit);
|
||||
final factory = createDatabaseFactoryFfi(
|
||||
ffiInit: SQfLiteEncryptionHelper.ffiInit,
|
||||
);
|
||||
|
||||
// required for [getDatabasesPath]
|
||||
databaseFactory = factory;
|
||||
|
|
@ -90,11 +88,7 @@ Future<MatrixSdkDatabase> _constructDatabase(String clientName) async {
|
|||
// to manage SQLite encryption
|
||||
final helper = cipher == null
|
||||
? null
|
||||
: SQfLiteEncryptionHelper(
|
||||
factory: factory,
|
||||
path: path,
|
||||
cipher: cipher,
|
||||
);
|
||||
: SQfLiteEncryptionHelper(factory: factory, path: path, cipher: cipher);
|
||||
|
||||
// check whether the DB is already encrypted and otherwise do so
|
||||
await helper?.ensureDatabaseFileEncrypted();
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ Future<String?> getDatabaseCipher() async {
|
|||
final list = Uint8List(32);
|
||||
list.setAll(0, Iterable.generate(list.length, (i) => rng.nextInt(256)));
|
||||
final newPassword = base64UrlEncode(list);
|
||||
await secureStorage.write(
|
||||
key: _passwordStorageKey,
|
||||
value: newPassword,
|
||||
);
|
||||
await secureStorage.write(key: _passwordStorageKey, value: newPassword);
|
||||
}
|
||||
// workaround for if we just wrote to the key and it still doesn't exist
|
||||
password = await secureStorage.read(key: _passwordStorageKey);
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ extension MatrixFileExtension on MatrixFile {
|
|||
await SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile.fromData(bytes, name: name, mimeType: mimeType)],
|
||||
sharePositionOrigin:
|
||||
box == null ? null : box.localToGlobal(Offset.zero) & box.size,
|
||||
sharePositionOrigin: box == null
|
||||
? null
|
||||
: box.localToGlobal(Offset.zero) & box.size,
|
||||
),
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -353,13 +353,15 @@ class MatrixLocals extends MatrixLocalizations {
|
|||
String get cancelledSend => l10n.sendCanceled;
|
||||
|
||||
@override
|
||||
String voiceMessage(String senderName, Duration? duration) =>
|
||||
l10n.sentVoiceMessage(
|
||||
senderName,
|
||||
duration == null
|
||||
? ''
|
||||
: '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}',
|
||||
);
|
||||
String voiceMessage(
|
||||
String senderName,
|
||||
Duration? duration,
|
||||
) => l10n.sentVoiceMessage(
|
||||
senderName,
|
||||
duration == null
|
||||
? ''
|
||||
: '${duration.inMinutes.toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}',
|
||||
);
|
||||
|
||||
@override
|
||||
String get refreshingLastEvent => l10n.loadingPleaseWait;
|
||||
|
|
|
|||
|
|
@ -21,19 +21,20 @@ bool _vodInitialized = false;
|
|||
|
||||
extension NotificationResponseJson on NotificationResponse {
|
||||
String toJsonString() => jsonEncode({
|
||||
'type': notificationResponseType.name,
|
||||
'id': id,
|
||||
'actionId': actionId,
|
||||
'input': input,
|
||||
'payload': payload,
|
||||
'data': data,
|
||||
});
|
||||
'type': notificationResponseType.name,
|
||||
'id': id,
|
||||
'actionId': actionId,
|
||||
'input': input,
|
||||
'payload': payload,
|
||||
'data': data,
|
||||
});
|
||||
|
||||
static NotificationResponse fromJsonString(String jsonString) {
|
||||
final json = jsonDecode(jsonString) as Map<String, Object?>;
|
||||
return NotificationResponse(
|
||||
notificationResponseType: NotificationResponseType.values
|
||||
.singleWhere((t) => t.name == json['type']),
|
||||
notificationResponseType: NotificationResponseType.values.singleWhere(
|
||||
(t) => t.name == json['type'],
|
||||
),
|
||||
id: json['id'] as int?,
|
||||
actionId: json['actionId'] as String?,
|
||||
input: json['input'] as String?,
|
||||
|
|
@ -55,8 +56,9 @@ Future<void> waitForPushIsolateDone() async {
|
|||
void notificationTapBackground(
|
||||
NotificationResponse notificationResponse,
|
||||
) async {
|
||||
final sendPort =
|
||||
IsolateNameServer.lookupPortByName(AppConfig.mainIsolatePortName);
|
||||
final sendPort = IsolateNameServer.lookupPortByName(
|
||||
AppConfig.mainIsolatePortName,
|
||||
);
|
||||
if (sendPort != null) {
|
||||
sendPort.send(notificationResponse.toJsonString());
|
||||
Logs().i('Notification tap sent to main isolate!');
|
||||
|
|
@ -80,8 +82,7 @@ void notificationTapBackground(
|
|||
final client = (await ClientManager.getClients(
|
||||
initialize: false,
|
||||
store: store,
|
||||
))
|
||||
.first;
|
||||
)).first;
|
||||
await client.abortSync();
|
||||
await client.init(
|
||||
waitForFirstSync: false,
|
||||
|
|
@ -111,8 +112,9 @@ Future<void> notificationTap(
|
|||
'Notification action handler started',
|
||||
notificationResponse.notificationResponseType.name,
|
||||
);
|
||||
final payload =
|
||||
FluffyChatPushPayload.fromString(notificationResponse.payload ?? '');
|
||||
final payload = FluffyChatPushPayload.fromString(
|
||||
notificationResponse.payload ?? '',
|
||||
);
|
||||
switch (notificationResponse.notificationResponseType) {
|
||||
case NotificationResponseType.selectedNotification:
|
||||
final roomId = payload.roomId;
|
||||
|
|
@ -182,16 +184,16 @@ Future<void> notificationTap(
|
|||
final avatarFile = avatar == null
|
||||
? null
|
||||
: await client
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
final messagingStyleInformation =
|
||||
await AndroidFlutterLocalNotificationsPlugin()
|
||||
.getActiveNotificationMessagingStyle(room.id.hashCode);
|
||||
|
|
|
|||
|
|
@ -89,8 +89,7 @@ Future<void> _tryPushHelper(
|
|||
client ??= (await ClientManager.getClients(
|
||||
initialize: false,
|
||||
store: await AppSettings.init(),
|
||||
))
|
||||
.first;
|
||||
)).first;
|
||||
final event = await client.getEventByPushNotification(
|
||||
notification,
|
||||
storeInDatabase: false,
|
||||
|
|
@ -105,8 +104,8 @@ Future<void> _tryPushHelper(
|
|||
// Make sure client is fully loaded and synced before dismiss notifications:
|
||||
await client.roomsLoading;
|
||||
await client.oneShotSync();
|
||||
final activeNotifications =
|
||||
await flutterLocalNotificationsPlugin.getActiveNotifications();
|
||||
final activeNotifications = await flutterLocalNotificationsPlugin
|
||||
.getActiveNotifications();
|
||||
for (final activeNotification in activeNotifications) {
|
||||
final room = client.rooms.singleWhereOrNull(
|
||||
(room) => room.id.hashCode == activeNotification.id,
|
||||
|
|
@ -168,16 +167,16 @@ Future<void> _tryPushHelper(
|
|||
roomAvatarFile = avatar == null
|
||||
? null
|
||||
: await client
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
.downloadMxcCached(
|
||||
avatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
} catch (e, s) {
|
||||
Logs().e('Unable to get avatar picture', e, s);
|
||||
}
|
||||
|
|
@ -185,18 +184,18 @@ Future<void> _tryPushHelper(
|
|||
senderAvatarFile = event.room.isDirectChat
|
||||
? roomAvatarFile
|
||||
: senderAvatar == null
|
||||
? null
|
||||
: await client
|
||||
.downloadMxcCached(
|
||||
senderAvatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
? null
|
||||
: await client
|
||||
.downloadMxcCached(
|
||||
senderAvatar,
|
||||
thumbnailMethod: ThumbnailMethod.crop,
|
||||
width: notificationAvatarDimension,
|
||||
height: notificationAvatarDimension,
|
||||
animated: false,
|
||||
isThumbnail: true,
|
||||
rounded: true,
|
||||
)
|
||||
.timeout(const Duration(seconds: 3));
|
||||
} catch (e, s) {
|
||||
Logs().e('Unable to get avatar picture', e, s);
|
||||
}
|
||||
|
|
@ -221,14 +220,15 @@ Future<void> _tryPushHelper(
|
|||
|
||||
final messagingStyleInformation = PlatformInfos.isAndroid
|
||||
? await AndroidFlutterLocalNotificationsPlugin()
|
||||
.getActiveNotificationMessagingStyle(id)
|
||||
.getActiveNotificationMessagingStyle(id)
|
||||
: null;
|
||||
messagingStyleInformation?.messages?.add(newMessage);
|
||||
|
||||
final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n));
|
||||
|
||||
final notificationGroupId =
|
||||
event.room.isDirectChat ? 'directChats' : 'groupChats';
|
||||
final notificationGroupId = event.room.isDirectChat
|
||||
? 'directChats'
|
||||
: 'groupChats';
|
||||
final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups;
|
||||
|
||||
final messageRooms = AndroidNotificationChannelGroup(
|
||||
|
|
@ -243,11 +243,13 @@ Future<void> _tryPushHelper(
|
|||
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.createNotificationChannelGroup(messageRooms);
|
||||
await flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>()
|
||||
?.createNotificationChannel(roomsChannel);
|
||||
|
||||
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
|
||||
|
|
@ -256,7 +258,8 @@ Future<void> _tryPushHelper(
|
|||
number: notification.counts?.unread,
|
||||
category: AndroidNotificationCategory.message,
|
||||
shortcutId: event.room.id,
|
||||
styleInformation: messagingStyleInformation ??
|
||||
styleInformation:
|
||||
messagingStyleInformation ??
|
||||
MessagingStyleInformation(
|
||||
Person(
|
||||
name: senderName,
|
||||
|
|
@ -288,9 +291,7 @@ Future<void> _tryPushHelper(
|
|||
FluffyChatNotificationActions.reply.name,
|
||||
l10n.reply,
|
||||
inputs: [
|
||||
AndroidNotificationActionInput(
|
||||
label: l10n.writeAMessage,
|
||||
),
|
||||
AndroidNotificationActionInput(label: l10n.writeAMessage),
|
||||
],
|
||||
cancelNotification: false,
|
||||
allowGeneratedReplies: true,
|
||||
|
|
@ -320,9 +321,11 @@ Future<void> _tryPushHelper(
|
|||
title,
|
||||
body,
|
||||
platformChannelSpecifics,
|
||||
payload:
|
||||
FluffyChatPushPayload(client.clientName, event.room.id, event.eventId)
|
||||
.toString(),
|
||||
payload: FluffyChatPushPayload(
|
||||
client.clientName,
|
||||
event.room.id,
|
||||
event.eventId,
|
||||
).toString(),
|
||||
);
|
||||
Logs().v('Push helper has been completed!');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,7 @@ extension ResizeImage on XFile {
|
|||
try {
|
||||
final bytes = await VideoCompress.getByteThumbnail(path);
|
||||
if (bytes == null) return null;
|
||||
return MatrixImageFile(
|
||||
bytes: bytes,
|
||||
name: name,
|
||||
);
|
||||
return MatrixImageFile(bytes: bytes, name: name);
|
||||
} catch (e, s) {
|
||||
Logs().w('Error while compressing video', e, s);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ extension RoomStatusExtension on Room {
|
|||
} else if (typingUsers.length == 1) {
|
||||
typingText = L10n.of(context).isTyping;
|
||||
if (typingUsers.first.id != directChatMatrixID) {
|
||||
typingText =
|
||||
L10n.of(context).userIsTyping(typingUsers.first.calcDisplayname());
|
||||
typingText = L10n.of(
|
||||
context,
|
||||
).userIsTyping(typingUsers.first.calcDisplayname());
|
||||
}
|
||||
} else if (typingUsers.length == 2) {
|
||||
typingText = L10n.of(context).userAndUserAreTyping(
|
||||
|
|
|
|||
|
|
@ -10,28 +10,25 @@ Future<T?> showScaffoldDialog<T>({
|
|||
double maxWidth = 480,
|
||||
double maxHeight = 720,
|
||||
required Widget Function(BuildContext context) builder,
|
||||
}) =>
|
||||
showDialog<T>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
builder: FluffyThemes.isColumnMode(context)
|
||||
? (context) => Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
color: containerColor ??
|
||||
Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.all(16),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
),
|
||||
child: builder(context),
|
||||
),
|
||||
)
|
||||
: builder,
|
||||
);
|
||||
}) => showDialog<T>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
builder: FluffyThemes.isColumnMode(context)
|
||||
? (context) => Center(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
color:
|
||||
containerColor ?? Theme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: const EdgeInsets.all(16),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: maxWidth,
|
||||
maxHeight: maxHeight,
|
||||
),
|
||||
child: builder(context),
|
||||
),
|
||||
)
|
||||
: builder,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ extension SyncStatusLocalization on SyncStatusUpdate {
|
|||
case SyncStatus.waitingForResponse:
|
||||
return L10n.of(context).waitingForServer;
|
||||
case SyncStatus.error:
|
||||
return ((error?.exception ?? Object()) as Object)
|
||||
.toLocalizedString(context);
|
||||
return ((error?.exception ?? Object()) as Object).toLocalizedString(
|
||||
context,
|
||||
);
|
||||
case SyncStatus.processing:
|
||||
case SyncStatus.cleaningUp:
|
||||
case SyncStatus.finished:
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ extension UiaRequestManager on MatrixState {
|
|||
final l10n = L10n.of(context);
|
||||
final navigatorContext =
|
||||
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ??
|
||||
context;
|
||||
context;
|
||||
try {
|
||||
if (uiaRequest.state != UiaRequestState.waitForUser ||
|
||||
uiaRequest.nextStages.isEmpty) {
|
||||
|
|
@ -27,7 +27,8 @@ extension UiaRequestManager on MatrixState {
|
|||
Logs().d('Uia Request Stage: $stage');
|
||||
switch (stage) {
|
||||
case AuthenticationTypes.password:
|
||||
final input = cachedPassword ??
|
||||
final input =
|
||||
cachedPassword ??
|
||||
(await showTextInputDialog(
|
||||
context: navigatorContext,
|
||||
title: l10n.pleaseEnterYourPassword,
|
||||
|
|
@ -87,9 +88,7 @@ extension UiaRequestManager on MatrixState {
|
|||
?.tryGet<String>('url');
|
||||
final fallbackUrl = client.homeserver!.replace(
|
||||
path: '/_matrix/client/v3/auth/$stage/fallback/web',
|
||||
queryParameters: {
|
||||
'session': uiaRequest.session,
|
||||
},
|
||||
queryParameters: {'session': uiaRequest.session},
|
||||
);
|
||||
final url = stageUrl != null
|
||||
? (Uri.tryParse(stageUrl) ?? fallbackUrl)
|
||||
|
|
@ -106,8 +105,9 @@ extension UiaRequestManager on MatrixState {
|
|||
|
||||
launchUrl(url, mode: LaunchMode.inAppBrowserView);
|
||||
final completer = Completer();
|
||||
final listener =
|
||||
AppLifecycleListener(onResume: () => completer.complete());
|
||||
final listener = AppLifecycleListener(
|
||||
onResume: () => completer.complete(),
|
||||
);
|
||||
await completer.future;
|
||||
listener.dispose();
|
||||
|
||||
|
|
|
|||
|
|
@ -99,13 +99,16 @@ class UrlLauncher {
|
|||
// okay, we have either an http or an https URI.
|
||||
// As some platforms have issues with opening unicode URLs, we are going to help
|
||||
// them out by punycode-encoding them for them ourself.
|
||||
final newHost = uri.host.split('.').map((hostPartEncoded) {
|
||||
final hostPart = Uri.decodeComponent(hostPartEncoded);
|
||||
final hostPartPunycode = punycodeEncode(hostPart);
|
||||
return hostPartPunycode != '$hostPart-'
|
||||
? 'xn--$hostPartPunycode'
|
||||
: hostPart;
|
||||
}).join('.');
|
||||
final newHost = uri.host
|
||||
.split('.')
|
||||
.map((hostPartEncoded) {
|
||||
final hostPart = Uri.decodeComponent(hostPartEncoded);
|
||||
final hostPartPunycode = punycodeEncode(hostPart);
|
||||
return hostPartPunycode != '$hostPart-'
|
||||
? 'xn--$hostPartPunycode'
|
||||
: hostPart;
|
||||
})
|
||||
.join('.');
|
||||
// Force LaunchMode.externalApplication, otherwise url_launcher will default
|
||||
// to opening links in a webview on mobile platforms.
|
||||
launchUrlString(
|
||||
|
|
@ -117,17 +120,17 @@ class UrlLauncher {
|
|||
void openMatrixToUrl() async {
|
||||
final matrix = Matrix.of(context);
|
||||
final url = this.url!.replaceFirst(
|
||||
AppConfig.deepLinkPrefix,
|
||||
AppConfig.inviteLinkPrefix,
|
||||
);
|
||||
AppConfig.deepLinkPrefix,
|
||||
AppConfig.inviteLinkPrefix,
|
||||
);
|
||||
|
||||
// The identifier might be a matrix.to url and needs escaping. Or, it might have multiple
|
||||
// identifiers (room id & event id), or it might also have a query part.
|
||||
// All this needs parsing.
|
||||
final identityParts = url.parseIdentifierIntoParts() ??
|
||||
final identityParts =
|
||||
url.parseIdentifierIntoParts() ??
|
||||
Uri.tryParse(url)?.host.parseIdentifierIntoParts() ??
|
||||
Uri.tryParse(url)
|
||||
?.pathSegments
|
||||
Uri.tryParse(url)?.pathSegments
|
||||
.lastWhereOrNull((_) => true)
|
||||
?.parseIdentifierIntoParts();
|
||||
if (identityParts == null) {
|
||||
|
|
@ -138,7 +141,8 @@ class UrlLauncher {
|
|||
// we got a room! Let's open that one
|
||||
final roomIdOrAlias = identityParts.primaryIdentifier;
|
||||
final event = identityParts.secondaryIdentifier;
|
||||
var room = matrix.client.getRoomByAlias(roomIdOrAlias) ??
|
||||
var room =
|
||||
matrix.client.getRoomByAlias(roomIdOrAlias) ??
|
||||
matrix.client.getRoomById(roomIdOrAlias);
|
||||
var roomId = room?.id;
|
||||
// we make the servers a set and later on convert to a list, so that we can easily
|
||||
|
|
@ -168,10 +172,7 @@ class UrlLauncher {
|
|||
// we have the room, so....just open it
|
||||
if (event != null) {
|
||||
context.go(
|
||||
'/${Uri(
|
||||
pathSegments: ['rooms', room.id],
|
||||
queryParameters: {'event': event},
|
||||
)}',
|
||||
'/${Uri(pathSegments: ['rooms', room.id], queryParameters: {'event': event})}',
|
||||
);
|
||||
} else {
|
||||
context.go('/rooms/${room.id}');
|
||||
|
|
@ -180,9 +181,8 @@ class UrlLauncher {
|
|||
} else {
|
||||
await showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (c) => PublicRoomDialog(
|
||||
roomAlias: identityParts.primaryIdentifier,
|
||||
),
|
||||
builder: (c) =>
|
||||
PublicRoomDialog(roomAlias: identityParts.primaryIdentifier),
|
||||
);
|
||||
}
|
||||
if (roomIdOrAlias.sigil == '!') {
|
||||
|
|
@ -223,12 +223,11 @@ class UrlLauncher {
|
|||
var noProfileWarning = false;
|
||||
final profileResult = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => matrix.client.getProfileFromUserId(userId).catchError(
|
||||
(_) {
|
||||
noProfileWarning = true;
|
||||
return Profile(userId: userId);
|
||||
},
|
||||
),
|
||||
future: () =>
|
||||
matrix.client.getProfileFromUserId(userId).catchError((_) {
|
||||
noProfileWarning = true;
|
||||
return Profile(userId: userId);
|
||||
}),
|
||||
);
|
||||
await UserDialog.show(
|
||||
context: context,
|
||||
|
|
|
|||
|
|
@ -45,8 +45,9 @@ class _VideoRendererState extends State<VideoRenderer> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
_streamChangeSubscription =
|
||||
widget.stream?.onStreamChanged.stream.listen((stream) {
|
||||
_streamChangeSubscription = widget.stream?.onStreamChanged.stream.listen((
|
||||
stream,
|
||||
) {
|
||||
setState(() {
|
||||
_renderer?.srcObject = stream;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,13 +33,15 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
|
|||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState? state) {
|
||||
background = (state == AppLifecycleState.detached ||
|
||||
background =
|
||||
(state == AppLifecycleState.detached ||
|
||||
state == AppLifecycleState.paused);
|
||||
}
|
||||
|
||||
void addCallingOverlay(String callId, CallSession call) {
|
||||
final context =
|
||||
kIsWeb ? ChatList.contextForVoip! : this.context; // web is weird
|
||||
final context = kIsWeb
|
||||
? ChatList.contextForVoip!
|
||||
: this.context; // web is weird
|
||||
|
||||
if (overlayEntry != null) {
|
||||
Logs().e('[VOIP] addCallingOverlay: The call session already exists?');
|
||||
|
|
@ -85,8 +87,7 @@ class VoipPlugin with WidgetsBindingObserver implements WebRTCDelegate {
|
|||
Future<RTCPeerConnection> createPeerConnection(
|
||||
Map<String, dynamic> configuration, [
|
||||
Map<String, dynamic> constraints = const {},
|
||||
]) =>
|
||||
webrtc_impl.createPeerConnection(configuration, constraints);
|
||||
]) => webrtc_impl.createPeerConnection(configuration, constraints);
|
||||
|
||||
Future<bool> get hasCallingAccount async => false;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue